常见的Java审计代码函数关键字_转载:Java代码审计汇总系列(一)——SQL注入
原文鏈接:https://cloud.tencent.com/developer/article/1534109
一、代碼審計(jì)
相比黑盒滲透的漏洞挖掘方式,代碼審計(jì)具有更高的可靠性和針對(duì)性,更多的是依靠對(duì)代碼、架構(gòu)的理解;使用的審計(jì)工具一般選擇Eclipse或IDEA;審計(jì)工作過(guò)程主要有三步:風(fēng)險(xiǎn)點(diǎn)發(fā)現(xiàn)——>風(fēng)險(xiǎn)定位追蹤——>漏洞利用,所以審計(jì)不出漏洞無(wú)非就是find:“找不到該看哪些代碼”和judge:“定位到代碼但判斷不出有沒(méi)有問(wèn)題”。而風(fēng)險(xiǎn)點(diǎn)發(fā)現(xiàn)的重點(diǎn)則在于三個(gè)地方:用戶輸入(入?yún)?處+檢測(cè)繞過(guò)處+漏洞觸發(fā)處,一般審計(jì)代碼都是借助代碼掃描工具(Fortify/Checkmarx)或從這三點(diǎn)著手。
本系列選取WebGoat作為案例,講解漏洞的特征發(fā)現(xiàn)、定位技巧、調(diào)試及觸發(fā)利用的具體過(guò)程,盡量涵蓋所有的挖掘場(chǎng)景,最后補(bǔ)充實(shí)戰(zhàn)挖掘案例。
二、SQLi漏洞挖掘
1、介紹
SQLi是最著名也是影響最廣的漏洞之一,注入漏洞都是程序把用戶輸入的數(shù)據(jù)當(dāng)做代碼執(zhí)行,發(fā)現(xiàn)的關(guān)鍵有兩個(gè),第一是用戶能夠控制輸入;第二是用戶輸入的數(shù)據(jù)被拼接到要執(zhí)行的代碼中從而被執(zhí)行。
2、挖掘過(guò)程
這里以webgoat的數(shù)字型注入講解SQLi漏洞的挖掘過(guò)程:
1) 定位特定功能模塊的代碼
了解不同框架特性,本系統(tǒng)的Springboot注解:
@RequestMapping(path= PATH)
@GetMapping(path= PATH)
@PostMapping(path= PATH)
通過(guò)抓取請(qǐng)求數(shù)據(jù)包獲取path特征SqlInjection/assignment5b:
使用IDEA的全局搜索功能(SHIFT+CTRL+F)定位到代碼:
2) 代碼分析
SqlInjectionLesson5b.java類代碼如下:
@PostMapping("/SqlInjection/assignment5b")
@ResponseBody
public AttackResult completed(@RequestParam String userid, @RequestParam String login_count, HttpServletRequest request) throws IOException {
return injectableQuery(login_count, userid);
}
protected AttackResult injectableQuery(String login_count, String accountName) {
String queryString = "SELECT * From user_data WHERE Login_Count = ? and userid= " + accountName;
try {
Connection connection = DatabaseUtilities.getConnection(getWebSession());
PreparedStatement query = connection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
這是一個(gè)典型的動(dòng)態(tài)拼接用戶輸入和防范案例,系統(tǒng)接收兩個(gè)參數(shù)login_count和userid,其中l(wèi)ogin_count通過(guò)“+”直接拼接,而userid首先通過(guò)類型轉(zhuǎn)換為Integer賦值給count,并經(jīng)過(guò)了預(yù)編譯(參數(shù)化請(qǐng)求)處理,不存在SQLi漏洞。
3)漏洞驗(yàn)證
最后構(gòu)造路徑及參數(shù)POC驗(yàn)證漏洞存在:
3、漏洞分類挖掘技巧
根據(jù)挖掘經(jīng)驗(yàn),白盒挖掘?qū)用娲笾驴梢詫QLi的類型分為六類:
1、入?yún)⒅苯觿?dòng)態(tài)拼接;
2、預(yù)編譯有誤;
3、框架注入(Mybatis+Hibernate);
4、order by 繞過(guò)預(yù)編譯;
5、%和_繞過(guò)預(yù)編譯;
6、SQLi檢測(cè)繞過(guò)
1) 參數(shù)直接拼接
最明顯的“+”拼接,思路一般有二:通過(guò)關(guān)鍵字定位到SQL語(yǔ)句,回溯參數(shù)是否是用戶可控;或通過(guò)跟蹤用戶輸入,是否執(zhí)行SQL操作,搜索的關(guān)鍵詞有:
Select|insert|update|delete|java.sql.Connection|Statement|.execute|.executeQuery|jdbcTemplate|queryForInt|queryForObject|queryForMap|getConnection|PreparedStatement|Statement|execute|jdbcTemplate|queryForInt|queryForObject|queryForMap|executeQuery|getConnection
2) 預(yù)編譯有誤
并不是使用了預(yù)編譯PreparedStatement一定就可以防止SQL注入,動(dòng)態(tài)拼接SQL同樣存在SQLi注入,這也是實(shí)際審計(jì)中高發(fā)的問(wèn)題,下面代碼就是典型的預(yù)編譯有誤:
String query = "SELECT * FROM usersWHERE userid ='"+ userid + "'" + " AND password='" +password + "'";
PreparedStatement stmt =connection.prepareStatement(query);
ResultSet rs = stmt.executeQuery();
定位預(yù)編譯可以通過(guò)搜索關(guān)鍵函數(shù):
setObject()、setInt()、setString()、setSQLXML()
3) 框架注入
Hibernate典型的注入代碼為:
session.createQuery("from Book wheretitle like '%" + userInput + "%' and published = true")
或形如:
{
StringBuffer queryString = newStringBuffer();
queryString.append(“from Test where id=’”);
queryString.append(id);
queryString.append(‘\’’);
}
定位此框架的SQL注入首先需要在xml配置文件或import包里確認(rèn)是否使用此框架,然后使用關(guān)鍵字createQuery,session.save(,session.update(,session.delete進(jìn)行定位。
Mybatis有兩種變量方法,不安全的寫法為:
select * from books where id= ${id}
安全的寫法為JDBC預(yù)編譯:
select * from books where id= #{id}
此外like、in和order by語(yǔ)句也需要使用#,挖掘技巧則是在注解中或者M(jìn)ybatis相關(guān)的配置文件中搜索 $。
4) order by 繞過(guò)預(yù)編譯
類似下面sql語(yǔ)句 order by 后面是不能用預(yù)編譯處理的只能通過(guò)拼接處理,只能手動(dòng)進(jìn)行過(guò)濾,詳見(jiàn)案例。
String sql = “Select * from news where title =?”+ “order by‘” + time + “’asc”
5) %和_繞過(guò)預(yù)編譯
預(yù)編譯是不能處理%,需要手動(dòng)過(guò)濾,否則會(huì)造成慢查詢和DOS。
6) SQLi檢測(cè)繞過(guò)
若SQL在處理過(guò)程中經(jīng)過(guò)黑/白名單(正則)或Filter檢測(cè),通常檢測(cè)代碼存在缺陷則可進(jìn)行檢測(cè)繞過(guò)。
4、漏洞防御
OWASP官方推薦的SQLi防御方案有四種:
1)預(yù)編譯(參數(shù)化查詢)
PreparedStatement stmt =connection.prepareStatement("SELECT * FROM users WHERE userid=? ANDpassword=?");
stmt.setString(1, userid);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
2)存儲(chǔ)過(guò)程
使用CallableStatement對(duì)存儲(chǔ)過(guò)程接口的實(shí)現(xiàn)來(lái)執(zhí)行數(shù)據(jù)庫(kù)查詢,SQL代碼定義并存儲(chǔ)在數(shù)據(jù)庫(kù)本身中,然后從應(yīng)用程序中調(diào)用,使用存儲(chǔ)過(guò)程和預(yù)編譯在防SQLi方面的效果是相同的。
String custname =request.getParameter("customerName");
try {
CallableStatement cs = connection.prepareCall("{callsp_getAccountBalance(?)}");
cs.setString(1, custname);
ResultSet results = cs.executeQuery();
} catch (SQLException se) {
}
3)黑/白名單驗(yàn)證
屬于輸入驗(yàn)證的范疇,大多使用正則表達(dá)式限制,或?qū)τ谥T如排序順序之類的簡(jiǎn)單操作,最好將用戶提供的輸入轉(zhuǎn)換為布爾值,然后將該布爾值用于選擇要附加到查詢的安全值。
public String someMethod(boolean sortOrder) {
String SQLquery = "someSQL ... order by Salary " + (sortOrder ? "ASC" :"DESC");`
4) 輸出轉(zhuǎn)義
將用戶輸入放入查詢之前對(duì)其進(jìn)行轉(zhuǎn)義,OWASP企業(yè)安全性API(ESAPI)是一個(gè)免費(fèi)的開(kāi)源Web應(yīng)用程序安全控制庫(kù)。
CodecORACLE_CODEC = new OracleCodec();
Stringquery = "SELECT user_id FROM user_data WHERE user_name = '"
+ESAPI.encoder().encodeForSQL( ORACLE_CODEC,req.getParameter("userID"))
+ "'and user_password = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC,req.getParameter("pwd")) +"'";
5)框架修復(fù):
對(duì)于Mybatis框架:
select * from news where tile like concat(‘%’,#{title}, ‘%’),
select * from news where id in
#{item}
Mybatis的order by語(yǔ)句可以選擇在java層做映射或過(guò)濾用戶輸入進(jìn)行防御。
對(duì)于Hibernate(HQL)框架預(yù)編譯:
方法一:
Query query=session.createQuery(“from Useruser where user.name=:customername and user:customerage=:age ”);
query.setString(“customername”,name);
query.setInteger(“customerage”,age);
方法二:
String hql ="FROM User user where user.name=? and user.age=?";
Query q =session.createQuery(hql);
q.setString(0, name);
q.setInteger(1,age);
5、實(shí)戰(zhàn)案例
1) Mybatis框架
對(duì)文章刪除功能進(jìn)行審計(jì),articelId參數(shù)前端可控:
@RequestMapping("/delete")
public ModelAndView delete(HttpServletRequestrequest) {
ModelAndView model = newModelAndView();
try {
model.setViewName(this.getRequestUri(request));
String[] aridArr = request.getParameterValues("articelId");
if (aridArr != null&& aridArr.length > 0) {
this.deleteArticle(aridArr);
}
} catch (Exception e) {
model.setViewName(this.setExceptionRequest(request,e));
logger.error("AdminArticleController.delete()--error",e);
}
return model;
}
順著變量的走向進(jìn)行審計(jì),articelId賦值給aridArr,而后進(jìn)行了為空的判斷,不為空則執(zhí)行deleteArticle操作,跟蹤定位此函數(shù):
private void deleteArticle(String[]artidArr) {
//刪除數(shù)據(jù)中記錄
articleService.deleteArticleByIds(artidArr);
EHCacheUtil.remove(CacheConstans.ARTICLE_GOOD_RECOMMEND);
繼續(xù)跟蹤操作,articleService類的deleteArticleByIds函數(shù),繼而進(jìn)入DAO層,在ArticleDaoImpl.java內(nèi):
public void deleteArticleByIds(StringarticleIds) {
this.delete("ArticleMapper.deleteArticleByIds",articleIds);
}
進(jìn)入ArticleMapper.xml,最終追蹤到deleteArticleByIds的SQL語(yǔ)句,使用了$拼接,典型的Mybatis注入:
DELETEFROM EDU_ARTICLE WHERE EDU_ARTICLE.ARTICLE_ID IN (${value})
除了順向思維,還可以通過(guò)逆向思維挖掘,pom.xml中看到系統(tǒng)使用的是Mybatis框架,可以直接去審查Maper.xml文件,查看是否使用$拼接:
UTF-8
3.2.12.RELEASE
3.2.7
1.7.3
1.7
2) Order by繞過(guò)預(yù)編譯
Webgoat一個(gè)order by的案例:
order by的參數(shù)orderExpression可以是一個(gè)selectExpression也可以是一個(gè)函數(shù),比如使用一個(gè)case語(yǔ)句。
案例中可以根據(jù)IP或ID對(duì)數(shù)據(jù)進(jìn)行排序:
對(duì)應(yīng)代碼為Server.java:
@GetMapping(produces =MediaType.APPLICATION_JSON_VALUE)
@SneakyThrows
@ResponseBody
public List sort(@RequestParamString column) {
Connection connection = DatabaseUtilities.getConnection(webSession);
PreparedStatement preparedStatement =connection.prepareStatement("select id, hostname, ip, mac, status, descriptionfrom servers where status <> 'outof order' order by " + column);
ResultSet rs = preparedStatement.executeQuery();
List servers = Lists.newArrayList();
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);
}
雖使用了預(yù)編譯但仍拼接了order by參數(shù)column,使用case探測(cè)語(yǔ)句探測(cè):(case when (true) then id else ip end),如果真則以id排序,結(jié)果為:
3) 預(yù)編譯有誤
查看FAQ頁(yè)面數(shù)據(jù)功能getFaqPage函數(shù),前端獲取page等參數(shù):
publicFaqPageInfo getFaqPage(String tenantId, Map conditions,int page, int pageSize, String like) {
try {
tenantId= WebUtil.getLoginTenantId();
FaqPageInfo fpi =FAQ_IO_SERVICE.getPageInfo(tenantId, conditions, page, pageSize, like);
for (FaqModel fm : fpi.getData()) {
fm.setWhitelistIds(WHITELIST_SERVICE.getWhiteListOnFaq(tenantId,fm.getId()));
}
return fpi;
}
跟蹤FAQ_IO_SERVICE.getPageInfo,調(diào)用FaqSqlAccess.queryByPage進(jìn)行處理:
public FaqPageInfo getPageInfo(StringtenantId, Map conditions, int page, int pageSize, Stringlike)
throws SQLException {
FaqPageInfo pageInfo = new FaqPageInfo();
pageInfo.setData(FaqSqlAccess.queryByPage(tenantId, conditions, page,pageSize, like));
pageInfo.setTotalSize(FaqSqlAccess.queryCount(tenantId, conditions,like));
return pageInfo;
}
找到FaqSqlAccess.java里的queryByPage方法,追蹤到SQL語(yǔ)句:
public static ListqueryByPage(String tenantId, Map conditions, int page,int pageSize, String like)
throws SQLException {
List models = new ArrayList<>();
String language = conditions.get("language");
Connection connection = null;
PreparedStatement stmt = null;
try {
connection = MysqlUtils.getConnection();
String sql = "select id, name, description, language, update_time,is_on from TOC_FAQ where tenant_id=?";
if (!CommonUtils.isEmptyStr(language))
sql += " andlanguage='" + language + "'";
if (!CommonUtils.isEmptyStr(like))
sql += " and name like'%" + like + "%'";
sql += " order by update_time desc limit ?,?";
stmt = connection.prepareStatement(sql);
stmt.setString(1, tenantId);
stmt.setInt(2, (page - 1) * pageSize);
stmt.setInt(3, pageSize);
ResultSet resultSet = stmt.executeQuery();
發(fā)現(xiàn)此處使用了預(yù)編譯,但language和like參數(shù)實(shí)際是直接拼接,存在SQL注入,對(duì)于getFaqPage功能構(gòu)造參數(shù)"language":"'-if(substring(user(),1,1)=0x01,sleep(5),0)-'"}進(jìn)行驗(yàn)證。
簡(jiǎn)單或復(fù)雜的SQL注入漏洞原理和審計(jì)方法相同,只是對(duì)于業(yè)務(wù)繁雜的系統(tǒng),數(shù)據(jù)的走向和處理過(guò)程會(huì)比較復(fù)雜,調(diào)用鏈跟蹤難度會(huì)稍大一些,需要更多耐心。
一、代碼審計(jì)
相比黑盒滲透的漏洞挖掘方式,代碼審計(jì)具有更高的可靠性和針對(duì)性,更多的是依靠對(duì)代碼、架構(gòu)的理解;使用的審計(jì)工具一般選擇Eclipse或IDEA;審計(jì)工作過(guò)程主要有三步:風(fēng)險(xiǎn)點(diǎn)發(fā)現(xiàn)——>風(fēng)險(xiǎn)定位追蹤——>漏洞利用,所以審計(jì)不出漏洞無(wú)非就是find:“找不到該看哪些代碼”和judge:“定位到代碼但判斷不出有沒(méi)有問(wèn)題”。而風(fēng)險(xiǎn)點(diǎn)發(fā)現(xiàn)的重點(diǎn)則在于三個(gè)地方:用戶輸入(入?yún)?處+檢測(cè)繞過(guò)處+漏洞觸發(fā)處,一般審計(jì)代碼都是借助代碼掃描工具(Fortify/Checkmarx)或從這三點(diǎn)著手。
本系列選取WebGoat作為案例,講解漏洞的特征發(fā)現(xiàn)、定位技巧、調(diào)試及觸發(fā)利用的具體過(guò)程,盡量涵蓋所有的挖掘場(chǎng)景,最后補(bǔ)充實(shí)戰(zhàn)挖掘案例。
二、SQLi漏洞挖掘
1、介紹
SQLi是最著名也是影響最廣的漏洞之一,注入漏洞都是程序把用戶輸入的數(shù)據(jù)當(dāng)做代碼執(zhí)行,發(fā)現(xiàn)的關(guān)鍵有兩個(gè),第一是用戶能夠控制輸入;第二是用戶輸入的數(shù)據(jù)被拼接到要執(zhí)行的代碼中從而被執(zhí)行。
2、挖掘過(guò)程
這里以webgoat的數(shù)字型注入講解SQLi漏洞的挖掘過(guò)程:
1) 定位特定功能模塊的代碼
了解不同框架特性,本系統(tǒng)的Springboot注解:
@RequestMapping(path= PATH)
@GetMapping(path= PATH)
@PostMapping(path= PATH)
通過(guò)抓取請(qǐng)求數(shù)據(jù)包獲取path特征SqlInjection/assignment5b:
使用IDEA的全局搜索功能(SHIFT+CTRL+F)定位到代碼:
2) 代碼分析
SqlInjectionLesson5b.java類代碼如下:
@PostMapping("/SqlInjection/assignment5b")
@ResponseBody
public AttackResult completed(@RequestParam String userid, @RequestParam String login_count, HttpServletRequest request) throws IOException {
return injectableQuery(login_count, userid);
}
protected AttackResult injectableQuery(String login_count, String accountName) {
String queryString = "SELECT * From user_data WHERE Login_Count = ? and userid= " + accountName;
try {
Connection connection = DatabaseUtilities.getConnection(getWebSession());
PreparedStatement query = connection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
這是一個(gè)典型的動(dòng)態(tài)拼接用戶輸入和防范案例,系統(tǒng)接收兩個(gè)參數(shù)login_count和userid,其中l(wèi)ogin_count通過(guò)“+”直接拼接,而userid首先通過(guò)類型轉(zhuǎn)換為Integer賦值給count,并經(jīng)過(guò)了預(yù)編譯(參數(shù)化請(qǐng)求)處理,不存在SQLi漏洞。
3)漏洞驗(yàn)證
最后構(gòu)造路徑及參數(shù)POC驗(yàn)證漏洞存在:
3、漏洞分類挖掘技巧
根據(jù)挖掘經(jīng)驗(yàn),白盒挖掘?qū)用娲笾驴梢詫QLi的類型分為六類:
1、入?yún)⒅苯觿?dòng)態(tài)拼接;
2、預(yù)編譯有誤;
3、框架注入(Mybatis+Hibernate);
4、order by 繞過(guò)預(yù)編譯;
5、%和_繞過(guò)預(yù)編譯;
6、SQLi檢測(cè)繞過(guò)
1) 參數(shù)直接拼接
最明顯的“+”拼接,思路一般有二:通過(guò)關(guān)鍵字定位到SQL語(yǔ)句,回溯參數(shù)是否是用戶可控;或通過(guò)跟蹤用戶輸入,是否執(zhí)行SQL操作,搜索的關(guān)鍵詞有:
Select|insert|update|delete|java.sql.Connection|Statement|.execute|.executeQuery|jdbcTemplate|queryForInt|queryForObject|queryForMap|getConnection|PreparedStatement|Statement|execute|jdbcTemplate|queryForInt|queryForObject|queryForMap|executeQuery|getConnection
2) 預(yù)編譯有誤
并不是使用了預(yù)編譯PreparedStatement一定就可以防止SQL注入,動(dòng)態(tài)拼接SQL同樣存在SQLi注入,這也是實(shí)際審計(jì)中高發(fā)的問(wèn)題,下面代碼就是典型的預(yù)編譯有誤:
String query = "SELECT * FROM usersWHERE userid ='"+ userid + "'" + " AND password='" +password + "'";
PreparedStatement stmt =connection.prepareStatement(query);
ResultSet rs = stmt.executeQuery();
定位預(yù)編譯可以通過(guò)搜索關(guān)鍵函數(shù):
setObject()、setInt()、setString()、setSQLXML()
3) 框架注入
Hibernate典型的注入代碼為:
session.createQuery("from Book wheretitle like '%" + userInput + "%' and published = true")
或形如:
{
StringBuffer queryString = newStringBuffer();
queryString.append(“from Test where id=’”);
queryString.append(id);
queryString.append(‘\’’);
}
定位此框架的SQL注入首先需要在xml配置文件或import包里確認(rèn)是否使用此框架,然后使用關(guān)鍵字createQuery,session.save(,session.update(,session.delete進(jìn)行定位。
Mybatis有兩種變量方法,不安全的寫法為:
select * from books where id= ${id}
安全的寫法為JDBC預(yù)編譯:
select * from books where id= #{id}
此外like、in和order by語(yǔ)句也需要使用#,挖掘技巧則是在注解中或者M(jìn)ybatis相關(guān)的配置文件中搜索 $。
4) order by 繞過(guò)預(yù)編譯
類似下面sql語(yǔ)句 order by 后面是不能用預(yù)編譯處理的只能通過(guò)拼接處理,只能手動(dòng)進(jìn)行過(guò)濾,詳見(jiàn)案例。
String sql = “Select * from news where title =?”+ “order by‘” + time + “’asc”
5) %和_繞過(guò)預(yù)編譯
預(yù)編譯是不能處理%,需要手動(dòng)過(guò)濾,否則會(huì)造成慢查詢和DOS。
6) SQLi檢測(cè)繞過(guò)
若SQL在處理過(guò)程中經(jīng)過(guò)黑/白名單(正則)或Filter檢測(cè),通常檢測(cè)代碼存在缺陷則可進(jìn)行檢測(cè)繞過(guò)。
4、漏洞防御
OWASP官方推薦的SQLi防御方案有四種:
1)預(yù)編譯(參數(shù)化查詢)
PreparedStatement stmt =connection.prepareStatement("SELECT * FROM users WHERE userid=? ANDpassword=?");
stmt.setString(1, userid);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
2)存儲(chǔ)過(guò)程
使用CallableStatement對(duì)存儲(chǔ)過(guò)程接口的實(shí)現(xiàn)來(lái)執(zhí)行數(shù)據(jù)庫(kù)查詢,SQL代碼定義并存儲(chǔ)在數(shù)據(jù)庫(kù)本身中,然后從應(yīng)用程序中調(diào)用,使用存儲(chǔ)過(guò)程和預(yù)編譯在防SQLi方面的效果是相同的。
String custname =request.getParameter("customerName");
try {
CallableStatement cs = connection.prepareCall("{callsp_getAccountBalance(?)}");
cs.setString(1, custname);
ResultSet results = cs.executeQuery();
} catch (SQLException se) {
}
3)黑/白名單驗(yàn)證
屬于輸入驗(yàn)證的范疇,大多使用正則表達(dá)式限制,或?qū)τ谥T如排序順序之類的簡(jiǎn)單操作,最好將用戶提供的輸入轉(zhuǎn)換為布爾值,然后將該布爾值用于選擇要附加到查詢的安全值。
public String someMethod(boolean sortOrder) {
String SQLquery = "someSQL ... order by Salary " + (sortOrder ? "ASC" :"DESC");`
4) 輸出轉(zhuǎn)義
將用戶輸入放入查詢之前對(duì)其進(jìn)行轉(zhuǎn)義,OWASP企業(yè)安全性API(ESAPI)是一個(gè)免費(fèi)的開(kāi)源Web應(yīng)用程序安全控制庫(kù)。
CodecORACLE_CODEC = new OracleCodec();
Stringquery = "SELECT user_id FROM user_data WHERE user_name = '"
+ESAPI.encoder().encodeForSQL( ORACLE_CODEC,req.getParameter("userID"))
+ "'and user_password = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC,req.getParameter("pwd")) +"'";
5)框架修復(fù):
對(duì)于Mybatis框架:
select * from news where tile like concat(‘%’,#{title}, ‘%’),
select * from news where id in
#{item}
Mybatis的order by語(yǔ)句可以選擇在java層做映射或過(guò)濾用戶輸入進(jìn)行防御。
對(duì)于Hibernate(HQL)框架預(yù)編譯:
方法一:
Query query=session.createQuery(“from Useruser where user.name=:customername and user:customerage=:age ”);
query.setString(“customername”,name);
query.setInteger(“customerage”,age);
方法二:
String hql ="FROM User user where user.name=? and user.age=?";
Query q =session.createQuery(hql);
q.setString(0, name);
q.setInteger(1,age);
5、實(shí)戰(zhàn)案例
1) Mybatis框架
對(duì)文章刪除功能進(jìn)行審計(jì),articelId參數(shù)前端可控:
@RequestMapping("/delete")
public ModelAndView delete(HttpServletRequestrequest) {
ModelAndView model = newModelAndView();
try {
model.setViewName(this.getRequestUri(request));
String[] aridArr = request.getParameterValues("articelId");
if (aridArr != null&& aridArr.length > 0) {
this.deleteArticle(aridArr);
}
} catch (Exception e) {
model.setViewName(this.setExceptionRequest(request,e));
logger.error("AdminArticleController.delete()--error",e);
}
return model;
}
順著變量的走向進(jìn)行審計(jì),articelId賦值給aridArr,而后進(jìn)行了為空的判斷,不為空則執(zhí)行deleteArticle操作,跟蹤定位此函數(shù):
private void deleteArticle(String[]artidArr) {
//刪除數(shù)據(jù)中記錄
articleService.deleteArticleByIds(artidArr);
EHCacheUtil.remove(CacheConstans.ARTICLE_GOOD_RECOMMEND);
繼續(xù)跟蹤操作,articleService類的deleteArticleByIds函數(shù),繼而進(jìn)入DAO層,在ArticleDaoImpl.java內(nèi):
public void deleteArticleByIds(StringarticleIds) {
this.delete("ArticleMapper.deleteArticleByIds",articleIds);
}
進(jìn)入ArticleMapper.xml,最終追蹤到deleteArticleByIds的SQL語(yǔ)句,使用了$拼接,典型的Mybatis注入:
DELETEFROM EDU_ARTICLE WHERE EDU_ARTICLE.ARTICLE_ID IN (${value})
除了順向思維,還可以通過(guò)逆向思維挖掘,pom.xml中看到系統(tǒng)使用的是Mybatis框架,可以直接去審查Maper.xml文件,查看是否使用$拼接:
UTF-8
3.2.12.RELEASE
3.2.7
1.7.3
1.7
2) Order by繞過(guò)預(yù)編譯
Webgoat一個(gè)order by的案例:
order by的參數(shù)orderExpression可以是一個(gè)selectExpression也可以是一個(gè)函數(shù),比如使用一個(gè)case語(yǔ)句。
案例中可以根據(jù)IP或ID對(duì)數(shù)據(jù)進(jìn)行排序:
對(duì)應(yīng)代碼為Server.java:
@GetMapping(produces =MediaType.APPLICATION_JSON_VALUE)
@SneakyThrows
@ResponseBody
public List sort(@RequestParamString column) {
Connection connection = DatabaseUtilities.getConnection(webSession);
PreparedStatement preparedStatement =connection.prepareStatement("select id, hostname, ip, mac, status, descriptionfrom servers where status <> 'outof order' order by " + column);
ResultSet rs = preparedStatement.executeQuery();
List servers = Lists.newArrayList();
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);
}
雖使用了預(yù)編譯但仍拼接了order by參數(shù)column,使用case探測(cè)語(yǔ)句探測(cè):(case when (true) then id else ip end),如果真則以id排序,結(jié)果為:
3) 預(yù)編譯有誤
查看FAQ頁(yè)面數(shù)據(jù)功能getFaqPage函數(shù),前端獲取page等參數(shù):
publicFaqPageInfo getFaqPage(String tenantId, Map conditions,int page, int pageSize, String like) {
try {
tenantId= WebUtil.getLoginTenantId();
FaqPageInfo fpi =FAQ_IO_SERVICE.getPageInfo(tenantId, conditions, page, pageSize, like);
for (FaqModel fm : fpi.getData()) {
fm.setWhitelistIds(WHITELIST_SERVICE.getWhiteListOnFaq(tenantId,fm.getId()));
}
return fpi;
}
跟蹤FAQ_IO_SERVICE.getPageInfo,調(diào)用FaqSqlAccess.queryByPage進(jìn)行處理:
public FaqPageInfo getPageInfo(StringtenantId, Map conditions, int page, int pageSize, Stringlike)
throws SQLException {
FaqPageInfo pageInfo = new FaqPageInfo();
pageInfo.setData(FaqSqlAccess.queryByPage(tenantId, conditions, page,pageSize, like));
pageInfo.setTotalSize(FaqSqlAccess.queryCount(tenantId, conditions,like));
return pageInfo;
}
找到FaqSqlAccess.java里的queryByPage方法,追蹤到SQL語(yǔ)句:
public static ListqueryByPage(String tenantId, Map conditions, int page,int pageSize, String like)
throws SQLException {
List models = new ArrayList<>();
String language = conditions.get("language");
Connection connection = null;
PreparedStatement stmt = null;
try {
connection = MysqlUtils.getConnection();
String sql = "select id, name, description, language, update_time,is_on from TOC_FAQ where tenant_id=?";
if (!CommonUtils.isEmptyStr(language))
sql += " andlanguage='" + language + "'";
if (!CommonUtils.isEmptyStr(like))
sql += " and name like'%" + like + "%'";
sql += " order by update_time desc limit ?,?";
stmt = connection.prepareStatement(sql);
stmt.setString(1, tenantId);
stmt.setInt(2, (page - 1) * pageSize);
stmt.setInt(3, pageSize);
ResultSet resultSet = stmt.executeQuery();
發(fā)現(xiàn)此處使用了預(yù)編譯,但language和like參數(shù)實(shí)際是直接拼接,存在SQL注入,對(duì)于getFaqPage功能構(gòu)造參數(shù)"language":"'-if(substring(user(),1,1)=0x01,sleep(5),0)-'"}進(jìn)行驗(yàn)證。
簡(jiǎn)單或復(fù)雜的SQL注入漏洞原理和審計(jì)方法相同,只是對(duì)于業(yè)務(wù)繁雜的系統(tǒng),數(shù)據(jù)的走向和處理過(guò)程會(huì)比較復(fù)雜,調(diào)用鏈跟蹤難度會(huì)稍大一些,需要更多耐心。
總結(jié)
以上是生活随笔為你收集整理的常见的Java审计代码函数关键字_转载:Java代码审计汇总系列(一)——SQL注入的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Acer笔记本电脑怎么开启投影功能?
- 下一篇: java criteria exist_