Author:jkgh006@敏信安全审计
DWR是一个远程web调用框架,利用该框架使得Ajax开发变得简单。利用DWR可以在客户端使用JavaScript直接调用服务器端的Java方法,并返回值给JavaScript;就好像直接在本地客户端调用一样(DWR根据Java类来动态生成JavaScript代码)
参考链接:http://directwebremoting.org/dwr/index.html
很多人私下问过我,如果现实审计中碰到dwr框架,应该怎么去构造payload,怎么根据流程分析出结果,所以这次我们只讲dwr在实际应用场景的审计和防御思路
根据官网给出来的默认配置如下web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app id="dwr">
<display-name>DWR (Direct Web Remoting)</display-name>
<description>A Simple Demo DWR</description>
<listener>
<listener-class>org.directwebremoting.servlet.DwrListener</listener-class>
</listener>
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<display-name>DWR Servlet</display-name>
<description>Direct Web Remoter Servlet</description>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>fileUploadMaxBytes</param-name>
<param-value>25000</param-value>
</init-param>
<!-- This should NEVER be present in live -->
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>accessLogLevel</param-name>
<param-value>runtimeexception</param-value>
</init-param>
<!-- Remove this unless you want to use active reverse ajax -->
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
<!-- By default DWR creates application scope objects when they are first
used. This creates them when the app-server is started -->
<init-param>
<param-name>initApplicationScopeCreatorsAtStartup</param-name>
<param-value>true</param-value>
</init-param>
<!-- WARNING: allowing JSON-RPC connections bypasses much of the security
protection that DWR gives you. Take this out if security is important -->
<init-param>
<param-name>jsonRpcEnabled</param-name>
<param-value>true</param-value>
</init-param>
<!-- WARNING: allowing JSONP connections bypasses much of the security
protection that DWR gives you. Take this out if security is important -->
<init-param>
<param-name>jsonpEnabled</param-name>
<param-value>true</param-value>
</init-param>
<!-- data: URLs are good for small images, but are slower, and could OOM for
larger images. Leave this out (or keep 'false') for anything but small images -->
<init-param>
<param-name>preferDataUrlSchema</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
上面的配置效果,再加上程序开发者的拿来主义习惯,可能会产生两个大问题
以上两个组合访问效果如下:
里面的每一个接口都可以通过浏览器抓包获取,常规意识下这些第三方调用未授权的概率几乎可以达到百分之百
为了方便对dwr的每一种类型进行测试,我们简单的编写了一些测试类
首先看commontest目录下,这里我们主要对int和string参数进行测试
package com.example.dwr.commontest;
public class CommonParams {
public static String stringTest(String data) {
return data;
}
public static int inTest(int data) {
return data;
}
}
在dwr.xml中加入
<create javascript="commonparams" creator="new">
<param name="class" value="com.example.dwr.commontest.CommonParams" />
</create>
这样在js调用层我们可以通过/dwr/interface/commonparams.js的commonparams变量调用java层编写的函数
例如index.jsp:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>dwr common test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="description" content="This is my page">
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/util.js'></script>
<script type='text/javascript' src='/dwr/interface/commonparams.js'></script>
<script type="text/javascript">
function stringTest(){
commonparams.stringTest("abcd");
}
function integerTest(){
commonparams.inTest(1234);
}
</script>
</head>
<body>
<input type="button" name="stringTest" onclick="stringTest()" value="stringTest">
<input type="button" name="integerTest" onclick="integerTest()" value="integerTest">
</body>
</html>
访问效果和http包如下
根据上下文判断dwr框架写法其实有一定的规律
上面调用的stringTest方法,如果我们调用的是inTest,只需要改这几个地方,数据类型定义成为int即可
POST /dwr/call/plaincall/commonparams.inTest.dwr HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Content-Type: text/plain
Referer: http://localhost:8080/
Content-Length: 212
Cookie: UM_distinctid=160cb8347c532e-02170ecaf6aeb-4c322f7c-1fa400-160cb8347c662d; CNZZDATA1261218610=1741751127-1515241945-%7C1515241945; JSESSIONID=DBEB32C68B89CE0D8815DB6ADF207376; DWRSESSIONID=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m
X-Forwarded-For: 127.0.0.1
Connection: close
callCount=1
nextReverseAjaxIndex=0
c0-scriptName=commonparams
c0-methodName=inTest
c0-id=0
c0-param0=int:1234
batchId=0
instanceId=0
page=%2F
scriptSessionId=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m/JZRRo9m-dCmbaYdn5
对array参数进行测试
package com.example.dwr.arraytest;
import org.apache.commons.lang.StringUtils;
public class ArrayParams {
public ArrayParams() {
}
public String iniArrayTest(int[] data) {
String template = "";
String tmp = "";
for(int i=0;i<data.length;i++) tmp = tmp+String.valueOf(data[i])+",";
template = template+"int array as:["+ tmp+"]";
return template;
}
public String strArrayTest(String[] data) {
String template = "";
String joinStr = StringUtils.join(data, ",");
template = template+"string array as:["+ joinStr+"]";
return template;
}
}
dwr.xml添加如下配置
<create javascript="arrayparams" creator="new">
<param name="class" value="com.example.dwr.arraytest.ArrayParams" />
</create>
例如arrtest.jsp:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>dwr arr test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="description" content="This is my page">
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/util.js'></script>
<script type='text/javascript' src='/dwr/interface/arrayparams.js'></script>
<script type="text/javascript">
function iniArrayTest(){
var a=[1,2,3,4];
arrayparams.iniArrayTest(a);
}
function strArrayTest(){
var a = ['a','b','c','d']
arrayparams.strArrayTest(a);
}
</script>
</head>
<body>
<input type="button" name="iniArrayTest" onclick="iniArrayTest()" value="iniArrayTest">
<input type="button" name="strArrayTest" onclick="strArrayTest()" value="strArrayTest">
</body>
</html>
访问和请求包如下:
post包的结构相对于普通类型是有所变化的,照猫画虎修改为strArrayTest函数
POST /dwr/call/plaincall/arrayparams.strArrayTest.dwr HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Content-Type: text/plain
Referer: http://localhost:8080/arrtest.jsp
Content-Length: 351
Cookie: UM_distinctid=160cb8347c532e-02170ecaf6aeb-4c322f7c-1fa400-160cb8347c662d; CNZZDATA1261218610=1741751127-1515241945-%7C1515241945; JSESSIONID=6B9103592284CBB4A787F99E8C21DE4A; DWRSESSIONID=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m
X-Forwarded-For: 127.0.0.1
Connection: close
callCount=1
nextReverseAjaxIndex=0
c0-scriptName=arrayparams
c0-methodName=strArrayTest
c0-id=0
c0-e1=string:a
c0-e2=string:b
c0-e3=string:c
c0-e4=string:d
c0-param0=array:[reference:c0-e1,reference:c0-e2,reference:c0-e3,reference:c0-e4]
batchId=1
instanceId=0
page=%2Farrtest.jsp
scriptSessionId=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m/WNnUo9m-uxKcm4x0i
package com.example.dwr.objecttest;
public class ObjectTest {
public ObjectTest() {
}
public String addUser(UserBean user){
return "Name:"+user.getName();
}
}
上面可以看出来传递的是一个java的bean对象,代码简单编写如下
package com.example.dwr.objecttest;
public class UserBean {
public String getName() { return name; }
public void setName(String name) { this.name = name; }
private String name;
}
此时我们就要用到dwr中的convert,作用就是通过Bean Converter将javascript变量user转变成java的UserBean类型
dwr.xml添加如下配置:
<create javascript="objecttest" creator="new">
<param name="class" value="com.example.dwr.objecttest.ObjectTest"/>
</create>
<convert match="com.example.dwr.objecttest.UserBean" converter="bean"/>
objtest的内容编写如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>dwr obj test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="description" content="This is my page">
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/util.js'></script>
<script type='text/javascript' src='/dwr/interface/objecttest.js'></script>
<script type="text/javascript">
function objectTest(){
var user={name:"jkgh006"};
objecttest.addUser(user);
}
</script>
</head>
<body>
<input type="button" name="objectTest" onclick="objectTest()" value="objectTest">
</body>
</html>
访问和请求包如下:
post包的结构这时候就变得复杂了,但是分析程序,还是可以简单的编写包结构
POST /dwr/call/plaincall/objecttest.addUser.dwr HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Content-Type: text/plain
Referer: http://localhost:8080/objtest.jsp
Content-Length: 271
Cookie: UM_distinctid=160cb8347c532e-02170ecaf6aeb-4c322f7c-1fa400-160cb8347c662d; CNZZDATA1261218610=1741751127-1515241945-%7C1515241945; JSESSIONID=14ED5B8693320646E08DD451F5F411D2; DWRSESSIONID=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m
X-Forwarded-For: 127.0.0.1
Connection: close
callCount=1
nextReverseAjaxIndex=0
c0-scriptName=objecttest
c0-methodName=addUser
c0-id=0
c0-e1=string:jkgh006
c0-param0=Object_Object:{name:reference:c0-e1}
batchId=1
instanceId=0
page=%2Fobjtest.jsp
scriptSessionId=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m/Y*v$o9m-3BE1kSgDb
package com.example.dwr.filetest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.directwebremoting.WebContext;
import org.directwebremoting.WebContextFactory;
public class FileTest {
public String upload(InputStream inputStream, String fileName) throws IOException {
String tempFileName= FilenameUtils.getName(fileName);
String path=getRealPath("upload");
File file=new File(path+ File.separator+tempFileName);
FileUtils.copyInputStreamToFile(inputStream, file);
return file.getPath();
}
public String getRealPath(String dir){
WebContext context= WebContextFactory.get();
return context.getSession().getServletContext().getRealPath(dir);
}
}
dwr.xml添加如下配置:
<create javascript="filtest" creator="new">
<param name="class" value="com.example.dwr.filetest.FileTest" />
</create>
filetest的内容编写如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>dwr file test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="description" content="This is my page">
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/util.js'></script>
<script type='text/javascript' src='/dwr/interface/filtest.js'></script>
<script type="text/javascript">
function upload(){
var file=dwr.util.getValue("file");
filtest.upload(file,file.value,function(result){
alert(result);
});
}
</script>
</head>
<body>
<input type="file" id="file" name="file"><br/>
<button onclick="upload();">uploadtest</button>
</body>
</html>
访问和请求包如下:
根据构造的包可以看出来,只是转换成了formdata形式,其他的构造都跟正常的差不多
POST /dwr/call/htmlcall/filtest.upload.dwr HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Content-Type: multipart/form-data; boundary=---------------------------252533190231463
Content-Length: 1414
Referer: http://localhost:8080/filetest.jsp
Cookie: UM_distinctid=160cb8347c532e-02170ecaf6aeb-4c322f7c-1fa400-160cb8347c662d; CNZZDATA1261218610=1741751127-1515241945-%7C1515241945; JSESSIONID=F647906117B5319F3161C493B1C03F95; DWRSESSIONID=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m
X-Forwarded-For: 127.0.0.1
Connection: close
Upgrade-Insecure-Requests: 1
-----------------------------252533190231463
Content-Disposition: form-data; name="callCount"
1
-----------------------------252533190231463
Content-Disposition: form-data; name="nextReverseAjaxIndex"
0
-----------------------------252533190231463
Content-Disposition: form-data; name="c0-scriptName"
filtest
-----------------------------252533190231463
Content-Disposition: form-data; name="c0-methodName"
upload
-----------------------------252533190231463
Content-Disposition: form-data; name="c0-id"
0
-----------------------------252533190231463
Content-Disposition: form-data; name="c0-param0"; filename="up.gif"
Content-Type: image/gif
1
-----------------------------252533190231463
Content-Disposition: form-data; name="c0-param1"
string:C%3A%5Cfakepath%5Cup.gif
-----------------------------252533190231463
Content-Disposition: form-data; name="batchId"
0
-----------------------------252533190231463
Content-Disposition: form-data; name="instanceId"
0
-----------------------------252533190231463
Content-Disposition: form-data; name="page"
%2Ffiletest.jsp
-----------------------------252533190231463
Content-Disposition: form-data; name="scriptSessionId"
J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m/52L6p9m-LLzEisz9f
-----------------------------252533190231463--
根据上面每种参数类型的单独举例,给出来一个包含除了文件外的所有参数的调用方式
package com.example.dwr.complextest;
import com.example.dwr.objecttest.UserBean;
import org.apache.commons.lang.StringUtils;
public class ComplexParams {
public ComplexParams() {
}
public String intAndStringAndArrayAndObjTest(int a, String b, int[] as, String[] bs,UserBean user,UserBean[] users) {
String template = "";
template = template+"int a:"+ String.valueOf(a)+"\n";
template = template+"string b:"+ String.valueOf(a)+"\n";
String tmp = "";
for(int i=0;i<as.length;i++) tmp = tmp+String.valueOf(as[i])+",";
template = template+"int array as:["+ tmp+"]\n";
String joinStr = StringUtils.join(bs, ",");
template = template+"string array as:["+ joinStr+"]\n";
return template;
}
}
上面已经存在参数嵌套了,不过没关系,不管多么复杂的参数结构,我们都可以构造出来
dwr.xml配置如下:
<create javascript="complexparams" creator="new">
<param name="class" value="com.example.dwr.complextest.ComplexParams" />
</create>
complextest的内容编写如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>dwr obj test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="description" content="This is my page">
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/util.js'></script>
<script type='text/javascript' src='/dwr/interface/complexparams.js'></script>
<script type="text/javascript">
function complexTest(){
var a = 1234;
var b = 'abcd';
var c = [1,2,3,4];
var d = ['a','b','c','d'];
var e ={name:"jkgh006"};
var f = [{name:"jkgh006"},{name:"jkgh007"},{name:"jkgh008"}]
complexparams.intAndStringAndArrayAndObjTest(a,b,c,d,e,f);
}
</script>
</head>
<body>
<input type="button" name="complexTest" onclick="complexTest()" value="complexTest">
</body>
</html>
查看到的构造包如下
请求后的构造包现在看起来就非常复杂了,但是构造思路在js里面还是比较清晰的,可以层层往下推
POST /dwr/call/plaincall/complexparams.intAndStringAndArrayAndObjTest.dwr HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Content-Type: text/plain
Referer: http://localhost:8080/complextest.jsp
Content-Length: 899
Cookie: UM_distinctid=160cb8347c532e-02170ecaf6aeb-4c322f7c-1fa400-160cb8347c662d; CNZZDATA1261218610=1741751127-1515241945-%7C1515241945; JSESSIONID=0E19EB66E71E4439A5CFD4FAF253BB3B; DWRSESSIONID=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m
X-Forwarded-For: 127.0.0.1
Connection: close
callCount=1
nextReverseAjaxIndex=0
c0-scriptName=complexparams
c0-methodName=intAndStringAndArrayAndObjTest
c0-id=0
c0-param0=number:1234
c0-param1=string:abcd
c0-e1=number:1
c0-e2=number:2
c0-e3=number:3
c0-e4=number:4
c0-param2=array:[reference:c0-e1,reference:c0-e2,reference:c0-e3,reference:c0-e4]
c0-e5=string:a
c0-e6=string:b
c0-e7=string:c
c0-e8=string:d
c0-param3=array:[reference:c0-e5,reference:c0-e6,reference:c0-e7,reference:c0-e8]
c0-e9=string:jkgh006
c0-param4=Object_Object:{name:reference:c0-e9}
c0-e11=string:jkgh006
c0-e10=Object_Object:{name:reference:c0-e11}
c0-e13=string:jkgh007
c0-e12=Object_Object:{name:reference:c0-e13}
c0-e15=string:jkgh008
c0-e14=Object_Object:{name:reference:c0-e15}
c0-param5=array:[reference:c0-e10,reference:c0-e12,reference:c0-e14]
batchId=0
instanceId=0
page=%2Fcomplextest.jsp
scriptSessionId=J2YAzcntFgQYepoW~g!fuZdxeAR6Qy4ho9m/uwpcp9m-VQn1SkeIe