前言
代码审计的思路
常见漏洞的代码审计
1. HTTP响应头截断
//未对响应头做任何安全处理,审计时为确认
String data;
if (data != null){
response.addHeader("Location", "/author.jsp?lang=" + data);
}
//同样的未对响应头做任何安全处理,审计时为确认
String author = request.getParameter(AUTHOR_PARAMETER);
// ...
Cookie cookie = new Cookie("author", author);
response.addCookie(cookie);
//使用Refenence类对环境变量值进行编码,剔除特殊字符,为误报
if (data != null){
String decode = Reference.decode(data);
response.addHeader("Location", "/author.jsp?lang=" + decode);
}
修复方案:
2. 硬编码问题
//fipAddress为硬编码
public class IPaddress
{
private String ipAddress = "172.16.254.1";
public static void main(String[] args)
{
//...
}
}
//SECRET_PASSWORD为硬编码
private String SECRET_PASSWORD = "No fear in my heart!";
Properties props = new Properties();
props.put(Context.SECURITY_CREDENTIALS, "password");
//1546272000000为硬编码
byte[] sr = hBaseClient.buildRowKey(devId, "1546272000000");
//qafgshw1900wxxxx为硬编码
private String accessKeyId = "qafgshw1900wxxxx";
//key值为硬编码
byte[] key = {1, 2, 3, 4, 5, 6, 7, 8};
SecretKeySpec spec = new SecretKeySpec(key, "AES");
Cipher aes = Cipher.getInstance("AES");
aes.init(Cipher.ENCRYPT_MODE, spec);
return aesCipher.doFinal(secretData);
//无法再继续追踪key值,为误报
public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception {
//key为密钥
byte[] keyBytes = decryptBASE64(key);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
//存储密钥。KeyStore.getInstance("PKCS12")为密钥库,为误报
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
Key key = keyGen.generateKey();
keyStore.setKeyEntry("secret", key, "password".toCharArray(), null);
keyStore.store(new FileOutputStream("output.p12"), "password".toCharArray());
} catch (Exception ex){
ex.printStackTrace();
}
3. SQL注入
@RequestMapping("/SqlInjection/{id}")
public ModelAndView SqlInjectTest(@PathVariable String id){
String mysqldriver = "com.mysql.jdbc.Driver";
String mysqlurl =
"jdbc:mysql://127.0.0.1:3306/test?user=root&password=123456&useUnicode=true&c
haracterEncoding=utf8&autoReconnect=true";#直接通过拼接 sql
String sql = "select * from user where id=" + id;
ModelAndView mav = new ModelAndView("test2");
try{
Class.forName(mysqldriver);
Connection conn = DriverManager.getConnection(mysqlurl);
PreparedStatement pstt = conn.prepareStatement(sql);
ResultSet rs = pstt.executeQuery();
//没有做任何其他安全处理措施
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from user where username = '" + username+"' and password='"+password+"'");
//只使用了占位符
@RequestMapping("/SqlInjection/{id}")
public ModelAndView SqlInjectTest(@PathVariable String id){
String mysqldriver = "com.mysql.jdbc.Driver";
String mysqlurl =
"jdbc:mysql://127.0.0.1:3306/test?user=root&password=123456&useUnicode=true&c
haracterEncoding=utf8&autoReconnect=true";
String sql = "select * from user where id= ?";
ModelAndView mav = new ModelAndView("test2");
try{
Class.forName(mysqldriver);
Connection conn = DriverManager.getConnection(mysqlurl);
PreparedStatement pstt = conn.prepareStatement(sql);
//pstt.setObject(1, id); //一般使用有误的是没有用这一句,编码者以为在上面的sql语句中直
接使用占位符就可以了。
ResultSet rs = pstt.executeQuery();
@RequestMapping("/SqlInjection/{id}")
public ModelAndView SqlInjectTest(@PathVariable String id){
String mysqldriver = "com.mysql.jdbc.Driver";
String mysqlurl =
"jdbc:mysql://127.0.0.1:3306/test?user=root&password=123456&useUnicode=true&c
haracterEncoding=utf8&autoReconnect=true";
String sql = "select * from user where id= ?";
ModelAndView mav = new ModelAndView("test2");
try{
Class.forName(mysqldriver);
Connection conn = DriverManager.getConnection(mysqlurl);
PreparedStatement pstt = conn.prepareStatement(sql);
pstt.setObject(1, id); //使用预编译
ResultSet rs = pstt.executeQuery();
String sql = “Select * from news where title =?”+ “order by ‘” + time +
“’asc”
//需要转义的字符串仍使用$
delete from ${tableName}
4. maven不安全模块
//3.9版本是存在漏洞的版本。安全版本是3.11以上
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-client</artifactId>
<version>3.9</version>
</dependency>
5. 服务端请求伪造(SSRF)
// Java
HttpURLConnection.getInputStream
URLConnection.getInputStream
Request.Get.execute
Request.Post.execute
URL.openStream
ImageIO.read
OkHttpClient.newCall.execute
HttpClients.execute
HttpClient.execute
//请求URL为外部可控,返回数据直接展示
String url = request.getParameter("picurl");
StringBuffer response = new StringBuffer();
URL pic = new URL(url);
HttpURLConnection con = (HttpURLConnection) pic.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
//发起请求,触发漏洞
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
modelMap.put("resp",response.toString());
return "getimg.htm";
//HttpClients函数的SSRF漏洞代码,审计时为确认:
String url = request.getParameter("url");
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = client.execute(httpGet); //发起请求
修复方案
6. 路径遍历
//没有对路径做任何安全处理措施
path = "config/"+path;
File file = new File(path);
System.out.println(path);
response.setHeader("Content-Disposition", "attachment;filename=\""
+ new String(path.getBytes(), "ISO8859-1") + "\"");
response.setContentLength((int) file.length());
byte[] buffer = new byte[4096];// 缓冲区
BufferedOutputStream output = null;
BufferedInputStream input = null;
//对下载的文件未做安全处理
public Response getImage(@javax.ws.rs.PathParam("image") String image) {
File file = new File("resources/images/", image);
if (!file.exists()) {
return Response.status(Status.NOT_FOUND).build();
}
return Response.ok().entity(new FileInputStream(file)).build();
}
7. 命令注入
// Java
Runtime.exec
ProcessBuilder.start
GroovyShell.evaluate
//没有对外部传入的命令参数command做任何安全处理
System.out.println("Command: ping"+ command);
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("cmd.exe /C ping "+command);
int res = proc.waitFor();
if(res !=0){
System.out.println("process error: "+ res);
}
InputStream in = (res == 0)? proc.getInputStream() : proc.getErrorStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
buffer=new StringBuffer();
String line;
while((line = reader.readLine())!=null){
buffer.append(line+"\n");
}
//同样没有对外部传入的命令拼接参数input做任何安全处理
Runtime r = Runtime.getRuntime();
r.exec("/bin/sh -c some_tool" + input);
//对外部传入的命令拼接参数做了限制
//正则限定为合法IP地址
if (!Pattern.matches("([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}", command)){
//如果不是IP则匹配不成功,则为F,!F则为T,T则执行此代码块
result.put("message", "Error!");
return DataUtil.toJson(result);
}
System.out.println("Command: ping"+ command);
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("cmd.exe /C ping "+command);
int res = proc.waitFor();
if(res !=0){
System.out.println("process error: "+ res);
}
InputStream in = (res == 0)? proc.getInputStream() : proc.getErrorStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
buffer=new StringBuffer();
String line;
while((line = reader.readLine())!=null){
buffer.append(line+"\n");
}
常见代码审计工具,代码审计为什么不能只用工具?
代码审计工具
代码审计工具优缺点
优点
缺点
总结
原文始发于微信公众号(龙哥网络安全):【网络安全安全管理入门必知必会】常见漏洞的代码审计方法,零基础入门到精通,收藏这一篇就够了
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论