Python 自动化 Web 应用扫描|第一部分详解

当我们谈到 web 应用扫描时,会想到各种攻击向量,如 SQL 注入、XSS、CSRF、LFI 和 RFI。当我们谈论 web 应用测试时,我们可能想到的工具是 Burp 套件。在本章中,我们将研究如何使用 Python 来尝试自动化 Web 应用的攻击向量检测。我们还将研究如何使用 Python 自动执行打嗝扫描,以覆盖我们必须手动发现的漏洞。在本章中,我们将研究以下主题:

Burp Suite Professional 在其 API 方面为笔式测试员提供了额外的功能。在 Burp Suite 专业 API 的帮助下,测试人员可以自动调用扫描并将其结果与其他工具集成。

Burp suite 目前通过其许可版本(Burp suite professional)提供 API 支持。这是所有网络安全专业人员必须具备的实用工具之一。为了最大限度地利用本章,我建议获得 Burp Suite 的许可版本。

启动 Burp Suite 并按如下方式配置 API:

然后,启动 API 并配置 API 密钥,如下所示:

当我们点击按钮时,键会被复制到剪贴板上。我们可以按如下方式使用它:

我们可以看到 API 正在监听端口1337。我们使用 API 键来引用这个端点地址。API 公开了三个端点:获取问题定义、启动扫描和获取正在运行的扫描的状态。

让我们看看我们需要哪些参数来启动新的扫描,以测试这个该死的易受攻击的 Web 应用。

可以从以下 URL 安装应用:

安装和设置后,我们可以使用以下curl命令在网站上启动带打嗝的活动扫描:

curl -vgw "\n" -X POST 'http://127.0.0.1:1337/<API KEY>/v0.1/scan' -d '{"application_logins":[{"password":"password","username":"admin"}],"name":"My first project","scan_configurations":[{"name":"Crawl strategy - fastest","type":"NamedConfiguration"}],"scope":{"exclude":[{"rule":"http://192.168.250.1/dvwa/logout.php","type":"SimpleScopeDef"}],"include":[{"rule":"http://192.168.250.1/dvwa","type":"SimpleScopeDef"}]},"urls":["http://192.168.250.1/dvwa/login.php"]}'

包含更详尽的爬网和审核测试的更一般的请求如下所示:

curl -vgw "\n" -X POST 'http://127.0.0.1:1337/<API KEY>/v0.1/scan' -d '{"application_logins":[{"password":"password","username":"admin"}],"scope":{"exclude":[{"rule":"http://192.168.250.1/dvwa/logout.php","type":"SimpleScopeDef"}],"include":[{"rule":"http://192.168.250.1/dvwa/","type":"SimpleScopeDef"}]},"urls":["http://192.168.250.1/dvwa/"]}'

需要注意的是,前面的请求可以通过 Ubuntu 上的终端发送,也可以使用 Burp API 提供的 web 界面生成请求。应该注意的是,如果以前面显示的方式调用请求,它将不会返回任何信息,而是使用任务 ID 创建一个新的扫描。

这可以在 Burp Suite 控制台上看到,如下所示:

在上一个屏幕截图中,我们可以看到一个 ID 为9的新任务已经创建,它正在扫描本地托管的易受攻击的 Web 应用。截图时,任务能够识别四个高问题、十个中问题和三个低问题。在下一节中,我们可以看到如何让扫描仪不断告诉我们扫描的状态。为了做到这一点,我们需要设置一个回调 URL。换句话说,我们需要一个监听端口,扫描仪将不断地发送结果。我们可以在控制台上按如下方式打印:

curl -vgw "\n" -X POST 'http://127.0.0.1:1337/Sm2fbfwrTQVqwH3VERLKIuXkiVbAwJgm/v0.1/scan' -d '{"application_logins":[{"password":"password","username":"admin"}],"scan_callback":{"url":"http://127.0.0.1:8000"},"scope":{"exclude":[{"rule":"http://192.168.250.1/dvwa/logout.php","type":"SimpleScopeDef"}],"include":[{"rule":"http://192.168.250.1/dvwa/","type":"SimpleScopeDef"}]},"urls":["http://192.168.250.1/dvwa/"]}'

扫描状态和所有结果将发送回指定地址:

既然我们现在已经了解了如何使用 Burp Suite API 自动化扫描,那么让我们编写一个 Python 脚本来实现这一点。我们将创建一个 Python 脚本来调用扫描,同时该脚本将侦听回调请求并解析响应以显示所有高、中、低问题。

让我们创建一个简单的 Python 脚本,并将其命名为burp_automate.py。输入以下代码:

import requests
import json
from urlparse import urljoin
import socket
import ast
import time
class Burp_automate():
    def __init__(self):
        self.result=""
        self.api_key="odTOmUX9mNTV3KRQ4La4J1pov6PEES72"
        self.api_url="http://127.0.0.1:1337"

    def start(self):
        try:

            data='{"application_logins":[{"password":"password","username":"admin"}],"scan_callback":{"url":"http://127.0.0.1:8001"},"scope":{"exclude":[{"rule":"http://192.168.250.1/dvwa/logout.php","type":"SimpleScopeDef"}],"include":[{"rule":"http://192.168.250.1/dvwa/","type":"SimpleScopeDef"}]},"urls":["http://192.168.250.1/dvwa/"]}'
            request_url=urljoin(self.api_url,self.api_key)
            request_url=str(request_url)+"/v0.1/scan"
            resp=requests.post(request_url,data=data)

            self.call_back_listener()
        except Exception as ex:
            print("EXception caught : " +str(ex))

    def poll_details(self,task_id):
        try:
            while 1:
                time.sleep(10)
                request_url=urljoin(self.api_url,self.api_key)
                request_url=str(request_url)+"/v0.1/scan/"+str(task_id)
                resp=requests.get(request_url)
                data_json=resp.json()

                issue_events=data_json["issue_events"]
                for issues in issue_events:

                    if issues["issue"]["severity"] != "info":
                        print("------------------------------------")
                        print("Severity : " + issues["issue"].get("severity",""))
                        print("Name : " + issues["issue"].get("name",""))
                        print("Path : " + issues["issue"].get("path",""))
                        print("Description : " + issues["issue"].get("description",""))
                        if issues["issue"].get("evidence",""):
                            print("URL : " + issues["issue"]["evidence"][0]["request_response"]["url"])
                        print("------------------------------------")
                        print("\n\n\n")
                if data_json["scan_status"]=="succeeded":
                    break

        except Exception as ex:
            print(str(ex))

    def call_back_listener(self):
        try:
            if 1 :
                task_id=0
                s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.bind(('127.0.0.1', 8001))
                s.listen(10)

                conn, addr = s.accept()

                if conn:
                    while True:
                        data = conn.recv(2048)
                        if not data:
                            break
                        try:
                            index=str(data).find("task_id")
                            task_id=str(data)[index:index+12]
                            task_id=task_id.replace('"',"")
                            splitted=task_id.split(":")
                            t_id=splitted[1]
                            t_id=t_id.lstrip().rstrip()
                            t_id=int(t_id)
                            if t_id:
                                task_id=t_id
                                break
                        except Exception as ex:
                            print("\n\n\nNot found" +str(ex))

                if task_id:
                    print("Task id : " +str(task_id))
                    self.poll_details(task_id)
                else:
                    print("No task id obtaimed,  Exiting : " )

        except Exception as ex:
            print("\n\n\n@@@@Call back exception :" +str(ex))

obj=Burp_automate()
obj.start()

当我们执行脚本时,它将显示打嗝扫描报告的所有问题,这些问题可能属于highmediumlow性质。

这显示在以下屏幕截图中:

以下屏幕截图表示扫描状态和请求总数。脚本将继续运行,直到扫描完成,状态为成功

SQL 注入攻击是一种攻击,使用该攻击可以更改 SQL 查询的执行以满足攻击者的需要。web 应用可能在后端与数据库进行交互,它可能会接受用户输入,这些输入构成参数或将要执行的 SQL 查询的一部分,以插入、删除、更新或检索数据库表中的数据。在这种情况下,开发人员必须非常小心,不要将用户提供的参数直接传递给后端数据库系统,因为这可能导致 SQL 注入。开发人员必须确保使用参数化查询。假设我们在应用上有一个登录页面,该页面接受用户的用户名和密码,并将此信息传递给支持的 SQL 查询:select * from users where email ='"+request.POST['email']+"' and password ='"+request.POST['password']"

应用中编写的逻辑将检查查询是否返回任何行。如果存在,则该用户是合法的,并且将为该用户分配一个有效会话,否则将显示显示错误消息Invalid credentials

假设用户将其电子邮件地址设置为admin@abc.com,密码设置为admin@123,在这种情况下,将在后端执行的查询如下:select * from users where email ='admin@abc.com' and password ='admin@123'

但是,如果用户以hacker@abc.com''1'='1的形式输入电子邮件,并且其密码为hacker''1'='1,则将在后端执行的查询将变为:select * from users where email ='hacker@abc.com' or '1'='1' and password ='hacker' or '1'='1'

因此,返回的数据集的第一条记录将被视为试图登录的用户,从而导致由于 SQL 注入而绕过身份验证。

我们在这里的重点是了解如何在 Python 的帮助下自动检测 SQL 注入。每当我们谈论 SQL 注入时,我们想到的工具就是 SQLmap,这是一个非常好的工具,是我个人最喜欢的在 web 应用中检测 SQL 注入的工具。互联网上有许多教程介绍如何使用 SQLmap 检测 SQL 注入。在本节中,我们将了解如何使用服务器版本的 SQLmap(它公开了一个 API)来自动化检测 SQL 注入漏洞的整个过程。我们将使用 Python 脚本来自动化检测过程。

让我们启动 SQLmap 服务器:

现在服务器已经启动并在本地主机上运行(端口8775),让我们看看如何使用 cURL 和 API 扫描应用(DVWA)进行 SQL 注入:

  • 创建一个新任务,如下所示:

  • 将新任务的scan选项设置如下:

  • 将新任务的list选项设置如下:

  • 使用以下set选项开始扫描:

  • 检查创建的扫描的status以发现 SQL 注入,如下所示:

前面的屏幕截图验证了后端数据库是 MySQL,并且参数 ID 易受 SQL 注入攻击。

让我们在 Python 脚本的帮助下自动化整个过程,如下所示。将脚本命名为sql_automate.py

import requests
import json
import time
import pprint

class SqliAutomate():

 def __init__(self,url,other_params={}):
 self.url=url
 self.other=other_params 

 def start_polling(self,task_id):
 try:
 time.sleep(30)
 poll_resp=requests.get("http://127.0.0.1:8775/scan/"+task_id+"/log")
 pp = pprint.PrettyPrinter(indent=4)
 #print(poll_resp.json())
 pp.pprint(poll_resp.json())
 except Exception as ex:
 print("Exception caught : " +str(ex))

 def start(self):
 try: 
 task_resp=requests.get("http://127.0.0.1:8775/task/new")
 data=task_resp.json()
 if data.get("success","") ==True:
 task_id=data.get("taskid")
 print("Task id : "+str(task_id))
 data_={'url':self.url}
 data_.update(self.other)
 opt_resp=requests.post("http://127.0.0.1:8775/option/"+task_id+"/set",json=data_)
 if opt_resp.json().get("success")==True:
 start_resp=requests.post("http://127.0.0.1:8775/scan/"+task_id+"/start",json=data_)
 if start_resp.json().get("success")==True:
 print("Scan Started successfully .Now polling\n")
 self.start_polling(task_id)
 except Exception as ex:
 print("Exception : "+str(ex))

other={'cookie':'PHPSESSID=7brq7o2qf68hk94tan3f14atg4;security=low'}
obj=SqliAutomate('http://192.168.250.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit',other)
obj.start()

让我们执行脚本并获取 SQL 注入的输出,如下所示:

root@thp3:~/sqli_automate# python sqli_automate.py
Task id : d0ba910ae1236ff4
Scan Started successfully .Now polling

{   u'log': [   {   u'level': u'INFO',
                    u'message': u'testing connection to the target URL',
                    u'time': u'13:13:15'},
                {   u'level': u'INFO',
                    u'message': u'checking if the target is protected by some kind of WAF/IPS/IDS',
                    u'time': u'13:13:15'},
                {   u'level': u'INFO',
                    u'message': u'testing if the target URL content is stable',
                    u'time': u'13:13:15'},
                {   u'level': u'INFO',
                    u'message': u'target URL content is stable',
                    u'time': u'13:13:16'},
                {   u'level': u'INFO',
                    u'message': u"testing if GET parameter 'id' is dynamic",
                    u'time': u'13:13:16'},
                {   u'level': u'WARNING',
                    u'message': u"GET parameter 'id' does not appear to be dynamic",
                    u'time': u'13:13:16'},
                {   u'level': u'INFO',
                    u'message': u"heuristic (basic) test shows that GET parameter 'id' might be injectable (possible DBMS: 'MySQL')",
                    u'time': u'13:13:16'},
                {   u'level': u'INFO',
                    u'message': u"heuristic (XSS) test shows that GET parameter 'id' might be vulnerable to cross-site scripting (XSS) attacks",
                    u'time': u'13:13:16'},
                {   u'level': u'INFO',
                    u'message': u"testing for SQL injection on GET parameter 'id'",
                    u'time': u'13:13:16'},
                {   u'level': u'INFO',
                    u'message': u"testing 'AND boolean-based blind - WHERE or HAVING clause'",
                    u'time': u'13:13:16'},
                {   u'level': u'WARNING',
                    u'message': u'reflective value(s) found and filtering out',
                    u'time': u'13:13:16'},
                {   u'level': u'INFO',
                    u'message': u"testing 'AND boolean-based blind - WHERE or HAVING clause (MySQL comment)'",
                    u'time': u'13:13:16'},
                {   u'level': u'INFO',
                    u'message': u"testing 'OR boolean-based blind - WHERE or HAVING clause (MySQL comment)'",
                    u'time': u'13:13:17'},
                {   u'level': u'INFO',
                    u'message': u"testing 'OR boolean-based blind - WHERE or HAVING clause (MySQL comment) (NOT)'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u'GET parameter \'id\' appears to be \'OR boolean-based blind - WHERE or HAVING clause (MySQL comment) (NOT)\' injectable (with --not-string="Me")',
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"GET parameter 'id' is 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' injectable ",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"testing 'MySQL inline queries'",
                    u'time': u'13:13:18'},
                {   u'level': u'INFO',
                    u'message': u"'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test",
                    u'time': u'13:13:28'},
                {   u'level': u'INFO',
                    u'message': u'target URL appears to have 2 columns in query',
                    u'time': u'13:13:29'},
                {   u'level': u'INFO',
                    u'message': u"GET parameter 'id' is 'MySQL UNION query (NULL) - 1 to 20 columns' injectable",
                    u'time': u'13:13:29'},
                {   u'level': u'WARNING',
                    u'message': u"in OR boolean-based injection cases, please consider usage of switch '--drop-set-cookie' if you experience any problems during data retrieval",
                    u'time': u'13:13:29'},
                {   u'level': u'INFO',
                    u'message': u'the back-end DBMS is MySQL',
                    u'time': u'13:13:29'}],
    u'success': True}

获得的输出可以解析并打印在屏幕上。

在本章中,我们讨论了使用 Python 自动化 web 应用扫描和评估的方法。我们了解了如何使用 Burp 套件 API 来用 Python 扫描底层应用,并研究了一组评估结果。我们还讨论了 SQL 注入以及 Python 如何与我们最喜欢的工具 SQLmap 一起使用。最后,我们研究了使用 Python 调用 SQLmap 以自动化 SQL 注入检测的整个过程。在下一章中,我们将了解如何使用 Python 自动检测其他 web 应用漏洞,如 XSS、CSRF、点击劫持和 SSL 条带。

  1. 使用 Burp 编写 Python 代码的其他方法有哪些?
  2. 其他哪些 SQL 注入工具可以通过 Python 实现自动化?
  3. 使用 web 应用扫描的自动化方法的缺点和优点是什么?

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

数据分析实战45讲 -〔陈旸〕

Java并发编程实战 -〔王宝令〕

Vue开发实战 -〔唐金州〕

分布式协议与算法实战 -〔韩健〕

OAuth 2.0实战课 -〔王新栋〕

Vim 实用技巧必知必会 -〔吴咏炜〕

Spring编程常见错误50例 -〔傅健〕

人人都用得上的数字化思维课 -〔付晓岩〕

深入浅出可观测性 -〔翁一磊〕