forked from Trietptm-on-Security/WooYun-2
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Android Content Provider Security.html
307 lines (208 loc) · 125 KB
/
Android Content Provider Security.html
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
<html>
<head>
<title>Android Content Provider Security - 瘦蛟舞</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>原文地址:<a href="http://drops.wooyun.org/tips/4314">http://drops.wooyun.org/tips/4314</a></h1>
<p>
<h2>0x00 科普</h2>
<hr />
<p>内容提供器用来存放和获取数据并使这些数据可以被所有的应用程序访问。它们是应用程序之间共享数据的唯一方法;不包括所有Android软件包都能访问的公共储存区域。Android为常见数据类型(音频,视频,图像,个人联系人信息,等等)装载了很多内容提供器。你可以看到在android.provider包里列举了一些。你还能查询这些提供器包含了什么数据。当然,对某些敏感内容提供器,必须获取对应的权限来读取这些数据。</p>
<p>如果你想公开你自己的数据,你有两个选择:你可以创建你自己的内容提供器(一个ContentProvider子类)或者你可以给已有的提供器添加数据,前提是存在一个控制同样类型数据的内容提供器且你拥有读写权限。</p>
<!--more-->
<p><img src="http://static.wooyun.org/20141212/2014121202372462932.jpg" alt="" /></p>
<h2>0x01 知识要点</h2>
<hr />
<p>参考:<a href="http://developer.android.com/guide/topics/providers/content-providers.html">http://developer.android.com/guide/topics/providers/content-providers.html</a></p>
<p><strong>Content URIs</strong></p>
<p>content URI 是一个标志provider中的数据的URI.Content URI中包含了整个provider的以符号表示的名字(它的authority) 和指向一个表的名字(一个路径).当你调用一个客户端的方法来操作一个provider中的一个表,指向表的content URI是参数之一.</p>
<p><img src="http://static.wooyun.org/20141212/2014121202373074404.jpg" alt="" /></p>
<p>A. 标准前缀表明这个数据被一个内容提供器所控制。它不会被修改。</p>
<p>B. URI的权限部分;它标识这个内容提供器。对于第三方应用程序,这应该是一个全称类名(小写)以确保唯一性。权限在<provider>元素的权限属性中进行声明:</p>
<pre><code> <provider name=".TransportationProvider"
authorities="com.example.transportationprovider"
. . . >
</code></pre>
<p>C. 用来判断请求数据类型的路径。这可以是0或多个段长。如果内容提供器只暴露了一种数据类型(比如,只有火车),这个分段可以没有。如果提供器暴露若干类型,包括子类型,那它可以是多个分段长-例如,提供"land/bus", "land/train", "sea/ship", 和"sea/submarine"这4个可能的值。</p>
<p>D. 被请求的特定记录的ID,如果有的话。这是被请求记录的_ID数值。如果这个请求不局限于单个记录, 这个分段和尾部的斜线会被忽略:</p>
<pre><code> content://com.example.transportationprovider/trains
</code></pre>
<p><strong>ContentResolver</strong></p>
<p>ContentResolver的方法们提供了对存储数据的基本的"CRUD" (增删改查)功能</p>
<pre><code>#!java
getIContentProvider()
Returns the Binder object for this provider.
delete(Uri uri, String selection, String[] selectionArgs) -----abstract
A request to delete one or more rows.
insert(Uri uri, ContentValues values)
Implement this to insert a new row.
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
Receives a query request from a client in a local process, and returns a Cursor.
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
Update a content URI.
openFile(Uri uri, String mode)
Open a file blob associated with a content URI.
</code></pre>
<p><strong>Sql注入</strong></p>
<p>sql语句拼接</p>
<pre><code>#!java
// 通过连接用户输入到列名来构造一个选择条款
String mSelectionClause = "var = " + mUserInput;
</code></pre>
<p>参数化查询</p>
<pre><code>#!java
// 构造一个带有占位符的选择条款
String mSelectionClause = "var = ?";
</code></pre>
<p><strong>权限</strong></p>
<p>下面的<uses-permission> 元素请求对用户词典的读权限:</p>
<pre><code><uses-permission android:name="android.permission.READ_USER_DICTIONARY">
</code></pre>
<p>申请某些protectionLevel="dangerous"的权限</p>
<pre><code><uses-permission android:name="com.huawei.dbank.v7.provider.DBank.READ_DATABASE"/>
<permission android:name="com.huawei.dbank.v7.provider.DBank.READ_DATABASE" android:protectionLevel="dangerous"></permission>
</code></pre>
<p>android:protectionLevel</p>
<p>normal:默认值。低风险权限,只要申请了就可以使用,安装时不需要用户确认。</p>
<p>dangerous:像WRITE_SETTING和SEND_SMS等权限是有风险的,因为这些权限能够用来重新配置设备或者导致话费。使用此protectionLevel来标识用户可能关注的一些权限。Android将会在安装程序时,警示用户关于这些权限的需求,具体的行为可能依据Android版本或者所安装的移动设备而有所变化。</p>
<p>signature:这些权限仅授予那些和本程序应用了相同密钥来签名的程序。</p>
<p>signatureOrSystem:与signature类似,除了一点,系统中的程序也需要有资格来访问。这样允许定制Android系统应用也能获得权限,这种保护等级有助于集成系统编译过程。</p>
<p><strong>API</strong></p>
<p>Contentprovider组件在API-17(android4.2)及以上版本由以前的exported属性默认ture改为默认false。</p>
<p>Contentprovider无法在android2.2(API-8)申明为私有。</p>
<pre><code><!-- *** POINT 1 *** Do not (Cannot) implement Private Content Provider in Android 2.2 (API Level 8) or earlier. -->
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17" />
</code></pre>
<p><strong>关键方法</strong></p>
<ul>
<li>public void addURI (String authority, String path, int code)</li>
<li>public static String decode (String s)</li>
<li>public ContentResolver getContentResolver()</li>
<li>public static Uri parse(String uriString)</li>
<li>public ParcelFileDescriptor openFile (Uri uri, String mode)</li>
<li>public final Cursor query(Uri uri, String[] projection,String selection, String[] selectionArgs, String sortOrder)</li>
<li>public final int update(Uri uri, ContentValues values, String where,String[] selectionArgs)</li>
<li>public final int delete(Uri url, String where, String[] selectionArgs)</li>
<li>public final Uri insert(Uri url, ContentValues values)</li>
</ul>
<h2>0x02 content provider 分类</h2>
<hr />
<p><img src="http://static.wooyun.org/20141212/2014121202373078881.jpg" alt="" /></p>
<p><img src="http://static.wooyun.org/20141212/2014121202373089005.jpg" alt="" /></p>
<p>这个老外分的特别细,个人认为就分private、public、in-house差不多够用。</p>
<h2>0x03 安全建议</h2>
<hr />
<ol>
<li>minSdkVersion不低于9</li>
<li>不向外部app提供的数据的私有content provider设置exported=“false”避免组件暴露(编译api小于17时更应注意此点)</li>
<li>使用参数化查询避免注入</li>
<li>内部app通过content provid交换数据设置protectionLevel=“signature”验证签名</li>
<li>公开的content provider确保不存储敏感数据</li>
<li>Uri.decode() before use ContentProvider.openFile()</li>
<li>提供asset文件时注意权限保护 </li>
</ol>
<h2>0x04 测试方法</h2>
<hr />
<p>1、反编译查看AndroidManifest.xml(drozer扫描)文件定位content provider是否导出,是否配置权限,确定authority</p>
<pre><code>#!bash
drozer:
run app.provider.info -a cn.etouch.ecalendar
</code></pre>
<p>2、反编译查找path,关键字<code>addURI</code>、hook api 动态监测推荐使用zjdroid</p>
<p>3、确定authority和path后根据业务编写POC、使用drozer、使用小工具Content Provider Helper、adb shell // 没有对应权限会提示错误</p>
<pre><code>#!bash
adb shell:
adb shell content query --uri <URI> [--user <USER_ID>] [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]
content query --uri content://settings/secure --projection name:value --where "name='new_setting'" --sort "name ASC"
adb shell content insert --uri content://settings/secure --bind name:s:new_setting --bind value:s:new_value
adb shell content update --uri content://settings/secure --bind value:s:newer_value --where "name='new_setting'"
adb shell content delete --uri content://settings/secure --where "name='new_setting'"
</code></pre>
<!--content query -–uri content://com.yulong.android.ntfcationmanager.provider/ntfpkgperm -->
<pre><code>#!bash
drozer:
run app.provider.query content://telephony/carriers/preferapn --vertical
</code></pre>
<h2>0x05 案例</h2>
<hr />
<p><strong>案例1:直接暴露</strong></p>
<ul>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-041595">WooYun: 盛大Youni有你Android版敏感信息泄露(可读用户本地消息)</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-016854">WooYun: 新浪微博Android应用本地信息泄露</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-021089">WooYun: 盛大起点读书Android客户端token等用户敏感信息泄露</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-039290">WooYun: 傲游浏览器限制不严格可导致网页欺诈攻击</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-042609">WooYun: 搜狗手机浏览器隐私泄露和主页篡改漏洞二合一(需要手机里有恶意应用)</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2014-085432">WooYun: 酷派S6流量监控绕过(偷跑流量不是事儿)</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2014-084500">WooYun: 酷派最安全手机s6通知栏管理权限绕过</a> </li>
</ul>
<p><strong>案例2:需权限访问</strong></p>
<ul>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-041521">WooYun: 米聊Android版敏感信息泄露(可读用户本地消息)</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2014-057590">WooYun: 华为网盘content provider组件可能泄漏用户信息</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-039697">WooYun: 人人客户端权限问题导致隐私泄露</a> </li>
</ul>
<p><strong>案例3:openFile文件遍历</strong></p>
<ul>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-044407">WooYun: 赶集网Android客户端Content Provider组件任意文件读取漏洞</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-047098">WooYun: 猎豹浏览器(Android版)任意私有文件数据可被本地第三方窃取漏洞</a> </li>
<li> <a target="_blank" href="http://www.wooyun.org/bugs/wooyun-2013-044411">WooYun: 58同城Android客户端远程文件写入漏洞</a> </li>
</ul>
<p>Override openFile method</p>
<p>错误写法1:</p>
<pre><code>#!java
private static String IMAGE_DIRECTORY = localFile.getAbsolutePath();
public ParcelFileDescriptor openFile(Uri paramUri, String paramString)
throws FileNotFoundException {
File file = new File(IMAGE_DIRECTORY, paramUri.getLastPathSegment());
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
</code></pre>
<p>错误写法2:URI.parse()</p>
<pre><code>#!java
private static String IMAGE_DIRECTORY = localFile.getAbsolutePath();
public ParcelFileDescriptor openFile(Uri paramUri, String paramString)
throws FileNotFoundException {
File file = new File(IMAGE_DIRECTORY, Uri.parse(paramUri.getLastPathSegment()).getLastPathSegment());
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
</code></pre>
<p>POC1:</p>
<pre><code>#!java
String target = "content://com.example.android.sdk.imageprovider/data/" + "..%2F..%2F..%2Fdata%2Fdata%2Fcom.example.android.app%2Fshared_prefs%2FExample.xml";
ContentResolver cr = this.getContentResolver();
FileInputStream fis = (FileInputStream)cr.openInputStream(Uri.parse(target));
byte[] buff = new byte[fis.available()];
in.read(buff);
</code></pre>
<p>POC2:double encode</p>
<pre><code>#!java
String target = "content://com.example.android.sdk.imageprovider/data/" + "%252E%252E%252F%252E%252E%252F%252E%252E%252Fdata%252Fdata%252Fcom.example.android.app%252Fshared_prefs%252FExample.xml";
ContentResolver cr = this.getContentResolver();
FileInputStream fis = (FileInputStream)cr.openInputStream(Uri.parse(target));
byte[] buff = new byte[fis.available()];
in.read(buff);
</code></pre>
<p>解决方法Uri.decode()</p>
<pre><code>#!java
private static String IMAGE_DIRECTORY = localFile.getAbsolutePath();
public ParcelFileDescriptor openFile(Uri paramUri, String paramString)
throws FileNotFoundException {
String decodedUriString = Uri.decode(paramUri.toString());
File file = new File(IMAGE_DIRECTORY, Uri.parse(decodedUriString).getLastPathSegment());
if (file.getCanonicalPath().indexOf(localFile.getCanonicalPath()) != 0) {
throw new IllegalArgumentException();
}
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
</code></pre>
<h2>0x06 参考</h2>
<hr />
<p><a href="https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=111509535">https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=111509535</a></p>
<p><a href="http://www.jssec.org/dl/android_securecoding_en.pdf">http://www.jssec.org/dl/android_securecoding_en.pdf</a></p>
<p><a href="http://developer.android.com/intl/zh-cn/reference/android/content/ContentProvider.html">http://developer.android.com/intl/zh-cn/reference/android/content/ContentProvider.html</a></p>
<h2>0x07 相关阅读</h2>
<p><a href="http://zone.wooyun.org/content/15097">http://zone.wooyun.org/content/15097</a></p>
<p><a href="http://drops.wooyun.org/tips/2997">http://drops.wooyun.org/tips/2997</a></p> </p>
</body>
</html>