干货|AsyncHttpClient为什么无法用Fiddler/BurpSuite/Charles来抓包的原理剖析
一、移动端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-android
https://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-http
https://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.client
DefaultHttpClient继承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
@Override
protected 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%