查看原文
其他

干货|AsyncHttpClient为什么无法用Fiddler/BurpSuite/Charles来抓包的原理剖析

云天实验室 哆啦安全 2022-05-24
前言
1.AsyncHttpClient为什么无法用Fiddler/BurpSuite/Charles来抓包 2.App挂代理无法抓包的各种原理讲解和通用解决方案总结 3.客户端证书处理逻辑分类 4.SSL Pinning原理 5.Frida绕过Android SSL Pinning(绕过原理) 6.利用Frida绕过Certificate Pinning 7.App挂代理抓包的通用解决方法 8.App防代理抓包策略总结

一、移动端App安全测试背景

Android App安全测试中,其中一个维度测试就是服务端业务逻辑安全性测试,主要通过抓包实现。


其实说白了就是中间人攻击


(1).安装着要测试App的Android手机发包给电脑的Fiddler/BurpSuite/Charles。

(2).然后可以使用Fiddler/BurpSuite/Charles截包改包的内容,再发给服务端,服务端收到请求后响应,把返回包发给电脑的Fiddler/BurpSuite/Charles。

(3).Fiddler/BurpSuite/Charles最后发给手机上的App。


随着移动端安全逐渐加强,现在越来越多的App已经无法抓到包,或者提示网络相关错误。其实根本原因在于客户端发包时对于服务端的SSL证书进行了校验。


二、httpclient-android和android-async-http源码解析、无法抓包的原理分析

https://github.com/smarek/httpclient-androidhttps://github.com/smarek/httpclient-android/releases
build.gradle配置implementation "cz.msebera.android:httpclient:4.5.8"

dependencies { implementation project("/path/to/generated/project/httpclient-android")}


https://github.com/android-async-http/android-async-httphttps://github.com/android-async-http/android-async-http/releases
build.gradle配置repositories { mavenCentral()}
dependencies { implementation 'com.loopj.android:android-async-http:1.4.11'}

repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }}
dependencies { implementation 'com.loopj.android:android-async-http:1.4.11-SNAPSHOT'}




干货|Android免Root最全Hook插件(Hook任意App)


AsyncHttpClient实际上是调用了,如下代码httpClient.getParams().setParameter("http.route.default-proxy",httpHost)
cz.msebera.android.httpclient.impl.clientDefaultHttpClient继承AbstractHttpClient



AbstractHttpClient的一次http请求过程中,将一个http请求提交到线程池之后,AsyncHttpRequest.java的run方法在执行过程中会调用

HttpResponse response = client.execute(request, context);


来完成一次完整的http请求,而这里的client即为DefaultHttpClient,看下client.execute方法到底是什么?

public CloseableHttpResponse execute( final HttpUriRequest request, final HttpContext context) throws IOException, ClientProtocolException { Args.notNull(request, "HTTP request"); return doExecute(determineTarget(request), request, context);}


通过层层关系,可以知道doExecute方法的执行者为AbstractHttpClient.java

@Overrideprotected final CloseableHttpResponse doExecute(final HttpHost target, final HttpRequest request, final HttpContext context) throws IOException, ClientProtocolException {
Args.notNull(request, "HTTP request"); // a null target may be acceptable, this depends on the route planner // a null context is acceptable, default context created below
HttpContext execContext = null; RequestDirector director = null; HttpRoutePlanner routePlanner = null; ConnectionBackoffStrategy connectionBackoffStrategy = null; BackoffManager backoffManager = null;
// Initialize the request execution context making copies of // all shared objects that are potentially threading unsafe. synchronized (this) {
final HttpContext defaultContext = createHttpContext(); if (context == null) { execContext = defaultContext; } else { execContext = new DefaultedHttpContext(context, defaultContext); } final HttpParams params = determineParams(request); final RequestConfig config = HttpClientParamConfig.getRequestConfig(params); execContext.setAttribute(ClientContext.REQUEST_CONFIG, config);
// Create a director for this request director = createClientRequestDirector( getRequestExecutor(), getConnectionManager(), getConnectionReuseStrategy(), getConnectionKeepAliveStrategy(), getRoutePlanner(), getProtocolProcessor(), getHttpRequestRetryHandler(), getRedirectStrategy(), getTargetAuthenticationStrategy(), getProxyAuthenticationStrategy(), getUserTokenHandler(), params); routePlanner = getRoutePlanner(); connectionBackoffStrategy = getConnectionBackoffStrategy(); backoffManager = getBackoffManager(); }
try { if (connectionBackoffStrategy != null && backoffManager != null) { final HttpHost targetForRoute = (target != null) ? target : (HttpHost) determineParams(request).getParameter( ClientPNames.DEFAULT_HOST); final HttpRoute route = routePlanner.determineRoute(targetForRoute, request, execContext);
final CloseableHttpResponse out; try { out = CloseableHttpResponseProxy.newProxy( director.execute(target, request, execContext)); } catch (final RuntimeException re) { if (connectionBackoffStrategy.shouldBackoff(re)) { backoffManager.backOff(route); } throw re; } catch (final Exception e) { if (connectionBackoffStrategy.shouldBackoff(e)) { backoffManager.backOff(route); } if (e instanceof HttpException) { throw (HttpException)e; } if (e instanceof IOException) { throw (IOException)e; } throw new UndeclaredThrowableException(e); } if (connectionBackoffStrategy.shouldBackoff(out)) { backoffManager.backOff(route); } else { backoffManager.probe(route); } return out; } else { return CloseableHttpResponseProxy.newProxy( director.execute(target, request, execContext)); } } catch(final HttpException httpException) { throw new ClientProtocolException(httpException); }}


重点看下第26~28行,这三行代码主要配置了当前http请求上下文中的一些参数

public static RequestConfig getRequestConfig(final HttpParams params) { return RequestConfig.custom() .setSocketTimeout(params.getIntParameter( CoreConnectionPNames.SO_TIMEOUT, 0)) .setStaleConnectionCheckEnabled(params.getBooleanParameter( CoreConnectionPNames.STALE_CONNECTION_CHECK, true)) .setConnectTimeout(params.getIntParameter( CoreConnectionPNames.CONNECTION_TIMEOUT, 0)) .setExpectContinueEnabled(params.getBooleanParameter( CoreProtocolPNames.USE_EXPECT_CONTINUE, false)) .setProxy((HttpHost) params.getParameter( ConnRoutePNames.DEFAULT_PROXY)) .setLocalAddress((InetAddress) params.getParameter( ConnRoutePNames.LOCAL_ADDRESS)) .setProxyPreferredAuthSchemes((Collection<String>) params.getParameter( AuthPNames.PROXY_AUTH_PREF)) .setTargetPreferredAuthSchemes((Collection<String>) params.getParameter( AuthPNames.TARGET_AUTH_PREF)) .setAuthenticationEnabled(params.getBooleanParameter( ClientPNames.HANDLE_AUTHENTICATION, true)) .setCircularRedirectsAllowed(params.getBooleanParameter( ClientPNames.ALLOW_CIRCULAR_REDIRECTS, false)) .setConnectionRequestTimeout((int) params.getLongParameter( ClientPNames.CONN_MANAGER_TIMEOUT, 0)) .setCookieSpec((String) params.getParameter( ClientPNames.COOKIE_POLICY)) .setMaxRedirects(params.getIntParameter( ClientPNames.MAX_REDIRECTS, 50)) .setRedirectsEnabled(params.getBooleanParameter( ClientPNames.HANDLE_REDIRECTS, true)) .setRelativeRedirectsAllowed(!params.getBooleanParameter( ClientPNames.REJECT_RELATIVE_REDIRECT, false)) .build();}


String SO_TIMEOUT = "http.socket.timeout"表示服务器响应超时,默认时间为0,单位毫秒
String STALE_CONNECTION_CHECK = "http.connection.stalecheck"表示是否对过期连接进行检查,关闭这个属性,一般可以提高30毫秒的性能
String CONNECTION_TIMEOUT = "http.connection.timeout"表示建立连接超时,默认时间为0,单位毫秒
String USE_EXPECT_CONTINUE = "http.protocol.expect-continue"表示是否激活Expect:100-Continue,默认值为false;备注:(Expect:100-Continue握手的目的,是为了允许客户端在发送请求内容之前,判断源服务器是否愿意接受请求(基于请求头部)。Expect:100-Continue握手需谨慎使用,因为遇到不支持HTTP/1.1协议的服务器或者代理时会引起问题。)
String DEFAULT_PROXY = "http.route.default-proxy"表示定义可以被不使用JRE设置的默认路由规划者使用的代理主机。这个参数期望得到一个HttpHost类型的值。如果这个参数没有被设置,那么就会尝试直接连接到目标,这个参数会被HttpRoutePlanner使用,HttpRoutePlanner是一个代表计算到基于执行上下文到给定目标完整路由策略的接口。HttpClient附带两个默认的HttpRoutePlanner实现。ProxySelectorRoutePlanner是基于java.net.ProxySelector的。默认情况下,它会从系统属性中或从运行应用程序的浏览器中选取JVM的代理设置。DefaultHttpRoutePlanner实现既不使用任何Java系统属性,也不使用系统或浏览器的代理设置。
String CONN_MANAGER_TIMEOUT = "http.conn-manager.timeout"表示从ClientConnectionManager中取出一个连接的时间,默认是为0,单位毫秒,在Apache的HttpClient中, ClientConnectionManager的默认实现为SingleClientConnManager,在SingleClientConnManager中,可以获取到ClientConnectionOperator,通过ClientConnectionOperator可以获取到http连接




重点看下第12行的参数设置,ConnRoutePNames.DEFAULT_PROXY,默认情况下是不使用代理进行路由寻址的,当我们通过,下面的方法,改变了路由寻址的策略,将wifi直连改为通过wifi代码去寻址;可以顺利的抓包

client.setProxy("172.20.10.5",8888);


三、App挂代理无法抓包的各种原理讲解和通用解决方案总结(可以提升至少3年的经验)

微信扫一扫付费阅读本文

可试读66%

微信扫一扫付费阅读本文

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存