宣传一波 更好的阅读体验 👉 个人blog
SQL Injection(mitigation)
1-4
介绍如何减小SQL注入的缺陷,使用PreparedStatement
贴一下源码
//Parameterized Queries - Java Snippet参数化查询(java代码片段)
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 ... }
//Parameterized Queries - Java Example(java案例)
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;
}
ai给出的解释:
PreparedStatement
是 Java JDBC 中的一个接口,它表示一个预编译的 SQL 语句。预编译的 SQL 语句可以在多次执行时重复使用,这可以提高执行效率。此外,PreparedStatement
还可以防止 SQL 注入攻击,因为它允许你将参数作为预编译语句的一部分,而不是直接将参数嵌入到 SQL 语句中。
使用 PreparedStatement
的主要好处是:
- 性能优化:预编译的 SQL 语句只需要被解析和编译一次,然后可以多次执行。这通常比使用
Statement
对象更快,因为它避免了重复的解析和编译开销。 - 防止 SQL 注入:通过使用参数化查询,你可以避免 SQL 注入攻击。当使用
PreparedStatement
时,参数值不会被解释为 SQL 代码,而是作为数据传递,从而提高了安全性。 - 灵活的查询:你可以使用
PreparedStatement
的setXXX()
方法(例如setString()
,setInt()
,setDouble
等)来设置参数值,这使得查询更加灵活和易于管理。
以下是一个简单的示例,演示如何使用 PreparedStatement
:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PreparedStatementExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydatabase";
String user = "username";
String password = "password";
String name = "John";
String email = "john@example.com";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
String sql = "SELECT status FROM users WHERE name=? AND mail=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, name);
ps.setString(2, email);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println("Status: " + rs.getString("status"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
在上面的示例中,我们首先创建一个到数据库的连接,然后准备一个预编译的 SQL 查询。接下来,我们使用 setString()
方法为占位符设置值,并执行查询。最后,我们遍历结果集并打印出状态信息。
5.
您可以在下面看到一些代码,但这些代码不完整。完成代码,使其不再容易受到 SQL 注入的攻击!使用您之前学过的类和方法。
代码必须根据用户的名称和邮件地址检索用户的状态。名称和邮件均采用字符串格式。
根据题意,使用PreparedStatement查询数据库
# 这行代码使用DriverManager类的getConnection方法来建立与数据库的连接。它需要三个参数:
# `DBURL`:数据库的URL或地址。
# `DBUSER`:用于连接到数据库的用户名。
# `DBPW`:用于连接到数据库的密码。通过这些参数,代码尝试与数据库建立连接,并将返回的`Connection`对象存储在名为`conn`的变量中。
Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPW);
# 这行代码创建了一个PreparedStatement对象。PreparedStatement是用来执行参数化SQL查询的。这里,它创建了一个查询,该查询从名为“users”的表中选择“status”字段,其中“name”和“mail”字段的值必须与给定的参数匹配。
# `?`:这些是参数占位符。在后续的代码中,我们将使用`setString`方法为这些占位符设置实际的值。使用参数化查询的好处是可以防止SQL注入攻击,并且可以更有效地与数据库通信。
PreparedStatement ps = conn.prepareStatement("SELECT status FROM users WHERE name
? AND mail=?");
# 这行代码为第一个参数占位符设置了值。它将第一个占位符的值设置为变量name的值。在这个上下文中,我们可以认为这是将查询中的“name”字段的值设置为某个特定的用户名。
ps.setString(1, name);
ps.setString(2, mail);
6.
试试吧!编写安全代码
现在是时候编写自己的代码了! 您的任务是使用 JDBC 连接到数据库并从中请求数据。
要求:
-
连接到数据库
-
对不受 SQL 注入攻击的数据库执行查询
-
查询需要包含至少一个字符串参数
开始之前的一些提示:
要连接到数据库,您可以简单地假设给定的常量 DBURL、DBUSER 和 DBPW。
查询的内容无关紧要,只要 SQL 有效且满足要求即可。
您编写的所有代码都将插入到已导入 java.sql.* 的名为“TestClass”的 Java 类的 main 方法中。
没有足够的创意来思考自己的问题?您尝试从名为 users 的虚构数据库表中检索具有特定名称的用户的数据怎么样?
例如:下面的代码可以编译而不会出现任何错误(但当然不符合完成本课的要求)。
try {
Connection conn = null;
System.out.println(conn); //should output 'null'
} catch (Exception e) {
System.out.println("Oops. Something went wrong!");
}
利用您的知识,在下面的编辑器窗口中从头开始编写一些有效的代码! (如果您无法在那里输入,调整一次浏览器窗口的大小可能会有所帮助,那么它应该可以工作):
因为已经导入所有包了,现在只需要写主要代码就行了
主要不要忘记ResultSet res = ps.executeQuery();
施行查询操作
ResultSet res = ps.executeQuery();
是在Java的JDBC(Java Database Connectivity)编程中常见的一行代码,它执行一个SQL查询并返回一个ResultSet
对象。
ps
是一个PreparedStatement
对象,它包含了一个预编译的SQL查询。executeQuery
是PreparedStatement
的一个方法,用于执行查询并返回一个ResultSet
对象。ResultSet
对象包含了查询的结果集,你可以使用ResultSet
的方法来获取和处理查询返回的数据。具体来说,这一行代码执行SQL查询,并将结果存储在
res
变量中,然后你可以使用这个ResultSet
对象来处理查询返回的数据。
try{
Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPW);
String sql = "select * from users where name = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "name");
ResultSet res = ps.executeQuery();
} catch (Exception e) {
System.out.println("Oops. Something went wrong!");
}
7
//Parameterized Queries - .NET
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... }
9.
给了个文章让我们去看
https://twitter.com/marcan42/status/1238004834806067200?s=21
但是我去看的时候发现文章没了。。。
直接看题吧
通过尝试发现过滤了空格
我们直接其他符号绕过就行了
我用的是 /**/
Smith'/**/UNION/**/SELECT/**/userid,user_name,/**/password,'a','b','c',1/**/from/**/user_system_data--'
然后用//
注释符号也行
10.
我们先尝试输入
发现每一次输入select 和 from在返回的结果中都会消失
说明这题除了过滤了空格,也过滤了关键字,即select,from,我们双写绕过就行了
原理:他只会过滤一次,即SELselectECt过滤一次会变成select,从而达到绕过的效果
Smith'/**/UNION/**/SELselectECT/**/userid,user_name,/**/password,'a','b','c',1/**/frfromom/**/user_system_data--'
12.
在此分配中,尝试通过 ORDER BY 字段执行 SQL 注入。 尝试找到服务器的 ip 地址,猜测完整的 IP 地址可能需要太长时间,因此我们为您提供最后一部分:webgoat-prdxxx.130.219.202
注意:此分配的提交字段不容易受到 SQL 注入的影响。
根据提示去按排序按钮,然后抓包,发现请求是get
发送给repeater,通过修改column发现排序是按值来的
输入非法字符报错,发现表名是servers
构造ORDER BY语句
①根据上一章节11说过,有order by (case when (true/false) then ip else hostname end
when为true,以ip排序。为false,以hostname排序。这两个字段的排序可以直接在网页上点击排序按钮查看。
②之前的SQL注入中,我们使用过substr(ip,1,1)=’1’语句,返回值是boolean类型,符合①
③如果直接将②语句嵌套到①中的when (①)的话,这里的ip是网页上的四个ip,webgoat-prd服务器还是被隐藏着,所以我们要把②中的“ip”替换成“select ip from servers where hostname=’webgoat-prd’”。
根据③②①结合得到ORDER BY语句如下
ORDER BY (CASE WHEN (substr((select ip from servers where hostname='webgoat-prd'),1,1)='1') THEN (ip) ELSE (hostname) END)
然后发送到intruder模块爆破
手动替换“,”=“%2C”,空格替换为“+”号
选择Cluster bomb,在第一个1和最后一个1标记
因为只需要爆破前三个ip值(后面的题目给了),然后后面值都是0-9,如此设置
然后爆破,查看response部分,如果排序是按ip则正确
最后可以得出IP为104.130.219.202
4.Path traversal
1.
Path traversal
A path(directory) traversal is a vulnerability where an attacker is able to access or store files and directories outside the location where the application is running. This may lead to reading files from other directories and in case of a file upload overwriting critical system files.
How does it work?
For example let’s assume we have an application which hosts some files and they can be requested in the following format: now as an attacker you are interested in other files of course so you try . In this case you try walk up to the root of the filesystem and then go into to gain access to this file. The is called dot-dot-slash which is another name for this attack.http://example.com/file=report.pdfhttp://example.com/file=../../../../../etc/passwd/etc/passwd../
Of course this is a very simple example and in most cases this will not work as frameworks implemented controls for this, so we need to get a little more creative and start encoding before the request is sent to the server. For example if we URL encode you will get and the web server receiving this request will decode it again to …/../%2e%2e%2f../
Also note that avoiding applications filtering those encodings double encoding might work as well. Double encoding might be necessary in the case where you have a system A which calls system B. System A will only decode once and will call B with the still encoded URL.
路径遍历
路径(目录)遍历是一种漏洞,攻击者能够访问或存储外部的文件和目录 应用程序运行的位置。这可能会导致从其他目录读取文件,如果是文件,则会导致读取文件 上传覆盖关键系统文件。
它是如何工作的?
例如,假设我们有一个应用程序,它托管了一些文件,并且可以在下面请求它们 格式: 现在,作为攻击者,您当然对其他文件感兴趣,所以 你试试.在这种情况下,您尝试爬到文件系统的根目录 然后进入以获取对此文件的访问权限。称为点-点-斜杠,这是另一个名称 对于这次攻击。http://example.com/file=report.pdfhttp://example.com/file=../../../../../etc/passwd/etc/passwd../
当然,这是一个非常简单的示例,在大多数情况下,这不会作为框架实现的控件 因此,我们需要更有创意,并在请求发送到服务器之前开始编码。 例如,如果我们对 URL 进行编码,您将得到并且接收此请求的 Web 服务器将解码 它再次更改为 …/../%2e%2e%2f../
另请注意,避免应用程序过滤这些编码的双重编码也可能有效。双重编码 如果系统 A 调用系统 B,则可能需要系统 A。系统 A 只会解码一次,并且 将使用仍编码的 URL 调用 B。
2.
上传文件,发现上传文件和FullName有关,即我们能控制上传文件位置
修改成../
只要不是正确的位置都能通过
3.
过滤了../
但是没有关系,我们换成../
就能通过了
4.
这题就需要抓包了
通过返回的数据可以看出储存的位置适合shell.php有关的,我们换成../shell.php
就行了
5.
使用路径遍历检索其他文件
路径遍历不仅限于文件上传,在检索文件时,路径遍历也可能是 可以从系统中检索其他文件。在此作业中,尝试查找一个名为path-traversal-secret.jpg
还是得抓包
按Show random cat picture后抓包
通过返回的数据可以知道?id=9.jpg
可以控制文件路径
我们照葫芦画瓢换成../
提心非法字符、
既然这样我们选择要转换的位置右键选择转换选择->URL->URL编码所有字符就能自动转换了
然后就是一步一步找了,在../../
文件位置找到(记得转换字符)
把id的值换成这个../../path-traveral-secert
(记得转换字符,而且这里需要把jpg去掉才行,否则不行,我这里没想明白。。。有评论区大佬请告知),然后就知道flag就似乎你的用户名用sha-512加密就行了
7.
Zip Slip 分配
这一次,开发人员只允许您上传 zip 文件,但是,他们犯了一个编程错误,因为上传 zip 文件会提取它,但不会替换您的图像。您能找到一种方法来绕过编程错误来覆盖当前映像吗?
这题我没想明白考点
我就直接找了一张照片压缩成zip上传就成功了。。。
本来还想抓包看看的
当我们去查看题目中的目录,发现ZIP已经被解压缩。这意味着,如果有相同文件名称的文件的话,将会被新文件覆盖。
正确的做法是,将所有上传的文件都经过一遍安全校验和安全设置,并将文件给一个强随机的文件名,避免文件覆盖问题。