学习
实践
活动
专区
工具
TVP
写文章

白露,宜凉风晨露学WebGoat 忌悲秋迷糊写BugC

图 火星 | 文 土星

今日白露,晨风清凉宜学习。观星给大家带来学习分享《 Java 代码审计入门篇:WebGoat 8(初见)》。

简介

WebGoat8 是基于 Spring boot 框架开发,故意不安全的 Web 应用程序,旨在教授Web 应用程序安全性课程。该程序演示了常见的服务器端应用程序缺陷。本文将简要分析 WebGoat8 的登陆模块,注册模块,作为热身,随后对 SQL 注入课程进行代码审计。

一、基础

本人对 WebGoat8 进行 review code 之时,仅有些许 Java 基础,了解过 Spring 框架,maven,对于 Spring boot 是第一次接触。所以本文是一个初学者面对新框架的条件下,做的一些探究记录。

二、准备

Java 11

Maven > 3.2.1

IDEA

注意,Java JDK 版本需要11,这个不能偷懒,亲测 Java8(burp suit运行环境)编译失败无法启动。需要做好 Java 版本切换准备。

下载源码

在 command shell 中

git clonehttps://github.com/WebGoat/WebGoat.git

编译项目

cd WebGoat

mvn clean install

运行项目

mvn -pl webgoat-server spring-boot:run

访问WebGoat

localhost:8080/WebGoat

三、import WebGoat到IDEA 进行代码查看及调试

选择Maven

Root directory选择WebGoat目录

勾选Maven porjects

选择SDK11

任意Project name

随后我们就能看到,配置都帮我们做好了,可以立马开始运行和调试的操作,一步到位!非常舒服。

这个时候你或许就想马上开始运行调试,然后发现报错了,或许这是因为前面你运行了mvn命令8080端口被占用的原因,关闭mvn即可。

四、组件安全

我们可以在IDEA左侧栏中查看到导入的包(组件)。通过查看这些组件及其版本,我们可以寻找是否引入了存在已知漏洞的组件。例如:Struts2(RCE等)、不安全的编辑控件(任意文件上传,xss等)、XML解析器(xxe)以及可被其它漏洞利用的如commons-collections:3.1(反序列化RCE)等等。

从下图我们也可以发现,稍微大一点的应用,组件都会非常多。一个个去搜索查看组件是否存在已知漏洞会变得非常不现实,因此,我们需要批量化工具去为我们检测组件安全。在这里,我们可以了解一下:OWASP-Dependency-Check

此次不对Dependency-check进行详细讲解,有兴趣的朋友可以访问以下链接了解:

https://www.owasp.org/index.php/OWASP_Dependency_Check

https://yq.aliyun.com/articles/698621

五、访问WebGoat8

http://127.0.0.1:8080/WebGoat/

简约大气的界面扑面而来。一访问WebGoat项目,就跳转到/login页面,我们需要看一下这个登陆认证的处理流程是怎么样的,从而思考是否存在安全问题。

六、Spring boot 登陆认证--WebSecurityConfig

问题

在代码中如何定位功能模块?

查找是否使用框架所提供对应的功能模块

通过路由定位功能模块

已知:

框架提供:Spring security 登录认证

Springboot路由「

@RequestMapping(path = PATH)

@GetMapping(path = PATH)

@PostMapping(path = PATH)

......

首先是尝试使用路由中的path特征“/login”,去全局搜索/login,可以找到WebSecurityConfig文件,通过查找资料也可以知道Spring boot可以通过编写WebSecurityConfig文件来设置相关的安全项(authentication和authorization),其中就包括了认证。所以可以非常确认WebSecurityConfig文件就是我们想要寻找的。

WebSecurityConfig.java

我们需要重点关注的代码块是:

翻阅AuthenticationManagerBuilder相关资料:

AuthenticationManagerBuilder用于创建AuthenticationManager。允许轻松构建内存身份验证,LDAP身份验证,基于JDBC的身份验证,添加UserDetailsService以及添加AuthenticationProvider。

基于内存身份认证:

我们可以看到,用户名密码直接写死在代码中然后运行时进入内存,当结合任意文件

读取,代码泄漏等漏洞时,可获取明文密码,这种做法是不安全的。

基于JDBC认证:

基于LDAP的认证:

基于自定义UserDetailsService认证:

由于WebGoat8就是基于自定义UserDetailsService认证,所以接下来重点关注一下这个方法。

然后我们追踪userDetailsService,如下图,即追踪源码中的UserService:

UserService.java:

可以看到是通过userRepository.findByUsername(username)去获取webGoatUser对象,对象里面就有username,password,role,user这几个元素,其中user是一个对象,后面将获取username,password,还有一些账号状态(过期,冻结等)的元素。

盲猜这个userRepository就是和数据库交互的对象,跟踪过去。

UserRepository.java

看到上面的代码,

是接口,没有具体的实现方法,怎么实现的?

又看到JpaRepository这个属于springframework的父类,去找找资料吧。

石器时代 定义数据源,创建JdbcTemplate,然后直接拼接SQL来查询。通用性很差。

工业革命 使用mybatis,定义数据源,定义domain,定义sql映射的xml文件,定义具体操作的DAO层。搭建起来很费时间,后期也经常需要维护。

现代化 用spring-data-jpa,简简单单继承JpaRepository,遵循spring data的规范定义查询接口,无需写实现类就可使用,大大减少数据访问层(DAO)的开发量。

简单来说,接口UserRepository extends JpaRepository下,

声明findByUsername(String username)方法,这个方法名findByUsername是按照规则去设计的,前缀findBy在解析的时候去掉,类似的前缀有:find、read、readBy、get、getBy,然后剩下Username(根据 POJO 规范,首字母变为小写)被判断是否为WebGoatUser对象的属性,是的话就进行查询,最终本质上转换为以下查询:

select u from WebGoatUser u where u.username = ?1

由于框架内动态绑定,是不会存在sql注入问题。amazing!非常现代化。

以上介绍的是通过解析方法名创建查询。通过这种方法查询一般不会有sql注入问题。此外还有:使用 @Query 创建查询

示范代码:

以上示范代码都是符合规范,不存在注入。

通过调用 JPA 命名查询语句创建查询

命名查询是 JPA 提供的一种将查询语句从方法体中独立出来,以供多个方法共用的功能。Spring Data JPA 对命名查询也提供了很好的支持。用户只需要按照 JPA 规范在 orm.xml 文件或者在代码中使用 @NamedQuery(或 @NamedNativeQuery)定义好查询语句,唯一要做的就是为该语句命名时,需要满足”DomainClass.methodName()”的命名规则。假设定义了如下接口:

如果希望为 findTop5() 创建命名查询,并与之关联,我们只需要在适当的位置定义命名查询语句,并将其命名为 "AccountInfo.findTop5",框架在创建代理类的过程中,解析到该方法时,优先查找名为 "AccountInfo.findTop5" 的命名查询定义,如果没有找到,则尝试解析方法名,根据方法名字创建查询。

因此识别出是JPA 命名查询语句创建查询的,需要去查看orm.xml查看定义的查询语句或在代码中查看@NamedQuery(或 @NamedNativeQuery)定义好查询语句是否安全。

总的来说:符合规范下JPA查询及其他数据库操作是比较安全的,但JPA也曾爆出漏洞(cve-2016-6652)感兴趣的朋友可以点击链接,查看详情。最后提一句,不是使用了厉害的框架,就能完全防止sql注入漏洞的,由于种种原因,在使用了安全的框架下,也会发生程序员使用直接拼接sql语句的情况。

回到AuthenticationManagerBuilder

追踪认证过程,我们可以知道,通过auth.userDetailsService(username),拿对应的对象(下图名为loadedUser),然后与从前端获取的username,password构成的authentication对象做对比,看credentials与password是否相等,决定了authenticated的值,从而完成认证过程。当然过程没有我说的那么简单,还有获取帐号的状态,然后根状态进行一系列操作的。

七、审计注册功能

通过/register.mvc定位代码

重点关注:

UserValidator.java

UserService.java

userRepository.java

UserTrackerRepository.java

总结:注册功能关键数据库操作部分使用JpaRepository作为父类的接口,编写符合规范,不存在注入问题。

八、SqlInjection

再次声明:WebGoat8是故意不安全的Web应用程序,旨在教授Web应用程序安全性课程。所以接下来朋友们看到的漏洞都是非常直接的,非常不贴近生产环境的。在现实情况中出现这么直接的漏洞的概率是非常低的。下面我们仅挑几个sql注入来看一下。

SqlInjection.lesson/1

看表输入SQL query语句,取出Bob Franco的信息。

直接将传入的query参数丢进去executeQuery,这个例子比参数拼接sql语句更暴力。

/SqlInjection.lesson/8

将account,operator,injection三个参数直接

SqlInjectionAdvanced.lesson/4

在这个例子中,我们看到提示:

We now explained the basic steps involved in an SQL injection. In this assignment you will need to combine all the things we explained in the SQL lessons.

Goal: Can you login as Tom?

使用tom的身份登陆

通读代码

SqlInjectionChallenge.java

在这一篇代码中,我们既能看到非常典型的拼接sql语句导致的sql注入,也能看到符合规范的使用preparedStatement去防止sql注入。

问题代码块:

直接将username_reg拼接到checkUserQuery,然后进行sql查询。查询到有结果则返回已存在用户,否则则使用PreparedStatement和占位符去声明insert语句,然后再使用setString去设置对应参数之后再执行。insert的过程没有问题,我们利用statement.executeQuery(checkUserQuery),以爆破的方式根据返回结果来获取tom的密码。

条件为真,查询到信息,所以返回“PAYLOAD already exists please try to register with a different username.”,"lessonCompleted" : false

条件为假,查询不到信息,返回“PAYLOAD ceated, please proceed to the login page.”,"lessonCompleted" : true

用length()爆破出密码长度为23

利用substring()写脚本去爆破密码

脚本:

本次文章到此结束,感谢您的翻阅,期待您的宝贵意见。

24节气观星帖

24节气观星帖是数字观星推出的节气小贴士,每月两期。每一期,我们会发布一张节气图,同时附带一些网络安全行业的小知识、小热点。

数字时代,基于威胁情报数据,为客户提供数字资产威胁管理 & 运营服务:

- 资产发现与管理

- 资产威胁监测与分析

- 漏洞全生命周期管理

“GuanXing您的数字资产安全”

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20190908A06N6W00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

关注

腾讯云开发者公众号
10元无门槛代金券
洞察腾讯核心技术
剖析业界实践案例
腾讯云开发者公众号二维码

扫码关注腾讯云开发者

领取腾讯云代金券