学习对象:“长城杯”信息安全铁人三项赛初赛样题
CTF(Capture The Flag)夺旗;抢旗;夺旗模式
webshell后门;网页木马;黑客后门
Webshell是指一种通过在Web服务器上上传恶意脚本文件的方式,获取Web服务器权限的攻击方式。黑客可以通过Webshell持续控制被攻击的服务器
防范Webshell攻击需要加强Web服务器的安全配置,及时更新安全补丁,限制服务器访问权限等。
"Reverse"指的是逆向工程(Reverse Engineering)。
逆向工程是指对软件、硬件或其他技术制品进行分析,以了解其设计、构造或运作方式的过程。
在计算机安全和CTF比赛中,逆向工程通常用于破解加密算法、分析恶意软件、还原程序逻辑等。
逆向工程可以帮助安全研究人员理解程序的运行方式,发现潜在的漏洞,并学习新的技术和知识
"Crypto" 是 "cryptography" 的缩写,指的是密码学。
密码学是研究如何保护信息安全的领域,它涉及使用各种算法和协议来加密和解密数据,以确保其机密性、完整性和可用性。
"MISC" 是 "Miscellaneous" 的缩写,意思是杂项或综合类别。
"MISC" 通常指的是一类与其他专门领域不太相关的题目或挑战,通常涵盖了多个主题和技术。
"Traffic" 在计算机安全和网络领域通常指的是流量或者数据包
"pwn" 通常指的是利用程序漏洞进行攻击或控制目标系统。
"Pwn" 题目通常要求参与者发现并利用程序中的漏洞,例如缓冲区溢出、格式化字符串漏洞、使用已知的漏洞利用技术(如ROP链、Return-Oriented Programming)等。通过成功利用漏洞,参与者可以获取目标系统的控制权,并实现特定的任务,例如获取权限、修改数据、执行代码等。
Fastbin attack
"Fastbin attack" 是一种针对堆(heap)的攻击技术,利用了快速分配(fastbin)内存管理机制中的漏洞。在C/C++程序中,快速分配是一种常见的内存分配机制,它通过维护多个不同大小的链表来管理已释放但尚未被重用的内存块。
Fastbin attack 的基本原理是通过伪造合适大小的堆块并构造堆溢出漏洞,使得程序将伪造的堆块链接到 fastbin 链表中。接着,攻击者可以通过多次分配和释放操作,绕过堆的内存分配检查,从而控制并修改返回的指针,实现任意内存写入或执行恶意代码的目的。
文件上传处理代码
总代码:
<!--
$sandbox = '/var/www/html/upload/' . md5("phpIsBest" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if(!empty($_FILES['file'])) {
#mime check
if (!in_array($_FILES['file']['type'], ['image/jpeg','image/png','image/gif'])) {
die('This type is not allowed!');
}
#check filename
$file (!is_array($file)) {
$file = explode('.', strtolower($file));
}
$ext = end($file);
if(!in_array($ext,['jpg','png','gif'])) {
die('This file is not allowed!');
}
$filename = reset($file).'.'.$file[count($file) - 1];
if (move_uploaded_file($_FILES['file']['tmp_name'], $sandbox . '/' . $filename)) {
echo 'Success!';
echo 'filepath:' . $sandbox . '/' .$filename;
} else {
echo 'Failed!';
}
}
-->
代码解析:
<!--
//和结尾最后的"-->"凑成一对。
/*
在PHP中,<!-- --> 是HTML注释的语法,而不是PHP代码的一部分。在PHP文件中,<!-- --> 将被视为普通文本而不会被解析为PHP代码。注释的内容不会在浏览器中显示
*/
$sandbox = '/var/www/html/upload/' . md5("phpIsBest" . $_SERVER['REMOTE_ADDR']);
/*
这部分首先构建了一个名为 $sandbox 的目录路径,目录路径由固定的前缀 '/var/www/html/upload/' 和用户 IP 地址经过 md5 哈希处理后的结果拼接而成。
*/
/*
在 PHP 中,$ 符号是一个变量标识符,用于声明或引用变量。
在 PHP 中,变量名可以以 $ 符号开头,后面跟着一个标识符(由字母、数字和下划线组成),以表示一个变量。
*/
@mkdir($sandbox);//然后尝试创建这个目录
@chdir($sandbox);//将当前工作目录切换到该目录
/*
在 PHP 中,@ 符号是一个错误控制运算符(Error Control Operator)。
它用于抑制代码中的错误或警告信息,使其不会被显示出来。当在一个表达式之前使用 @ 符号时,如果该表达式发生错误,PHP 将忽略该错误并继续执行后面的代码,而不会中断程序执行。
举例说明:
在 $sandbox 目录创建的代码中,@mkdir($sandbox); 使用了 @ 符号。这意味着如果 mkdir() 函数调用中出现错误,例如:由于权限问题无法创建目录,PHP 将不会显示错误消息,并且代码将继续执行下去。
*/
/*
接下来是文件上传的处理部分。首先检查是否有文件被上传,然后进行了两方面的检查:
MIME 类型检查 & 文件名检查。
*/
if(!empty($_FILES['file']))
/*
名为“file”的文件
!empty($_FILES['file']) 是一个条件表达式,用于检查是否上传了名为 'file' 的文件。具体来说,它使用了 PHP 中的两个函数:empty() 和 $_FILES。
empty() 函数用于检查一个变量是否为空。如果变量为空,则返回 true,否则返回 false。在这里,empty($_FILES['file']) 检查名为 'file' 的文件是否被上传到服务器。
$_FILES 是一个超级全局数组,用于在 PHP 中处理 HTTP 文件上传。该数组包含了上传文件的相关信息,如文件名、文件类型、文件大小等。在这里,$_FILES['file'] 表示上传到服务器的名为 'file' 的文件的相关信息。
理解1:
(!,否定) + (empty,空的) = 不是空的 -> 有东西,有名为'file'的文件被上传到服务器
->
if(!empty($_FILES['file'])) :如果名为 'file' 的文件成功上传到服务器,则执行 if 语句内的代码块,返回 true,否则返回 false。
理解2:
如果名为 'file' 的文件被成功上传到服务器,!empty($_FILES['file']) 将返回 true。这是因为 empty() 函数在检查一个变量是否为空时,如果变量不为空,即非空,则返回 false,然后 ! 取反运算符将 false 转换为 true。
*/
{
#mime check,MIME 类型检查:
if (!in_array($_FILES['file']['type'], ['image/jpeg','image/png','image/gif'])) {
die('This type is not allowed!');
}
/*
MIME 类型检查:
$_FILES 是一个特殊的超全局变量,用于处理上传的文件。$_FILES['file'] 表示上传的名为 'file' 的文件的相关信息,其中 'type' 属性表示上传文件的 MIME 类型。因此,$_FILES['file']['type'] 表示上传文件的 MIME 类型。
检查上传文件的 MIME 类型是否为 'image/jpeg(JPEG 图片)'、'image/png( PNG 图片)' 或 'image/gif( GIF 图片)',如果不是,则输出错误信息并终止程序。
in_array() 函数用于检查一个值是否存在于数组中。在这里,它的作用是检查 $_FILES['file']['type'] 是否在给定的类型数组 ['image/jpeg', 'image/png', 'image/gif'] 中。
in_array() 函数 它的使用格式如下:
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
参数 $needle 是要查找的值,可以是任何数据类型。参数 $haystack 是待搜索的数组,是一个包含多个元素的数组。可选参数 $strict 是一个布尔值,表示是否进行严格比较,默认为 false。
函数返回值是一个布尔值,如果 $needle 存在于 $haystack 数组中,则返回 true,否则返回 false。
*/
/*
MIME 是一种标准,用于在互联网上对多媒体和其他类型的数据进行标识和描述。MIME(Multipurpose Internet Mail Extensions)中文名称为“多用途互联网邮件扩展”。
MIME 类型检查是指通过检查文件的扩展名或内容来确定文件的 MIME 类型。每个文件都有一个关联的 MIME 类型,它指示了文件的内容类型。
MIME 类型将帮助浏览器和其他应用程序正确解释和处理文件。通过检查文件的扩展名或内容,可以确定文件的 MIME 类型,并相应地处理它。这在上传文件、下载文件或处理文件时非常有用。
例如,常见的 MIME 类型包括:
text/plain:普通文本文件
text/html:HTML 文件
application/pdf:PDF 文件
image/jpeg:JPEG 图像文件
audio/mp3:MP3 音频文件
video/mp4:MP4 视频文件
*/
#check filename文件名检查
$file (!is_array($file)) {
$file = explode('.', strtolower($file));
}
/*
这段代码的作用是将 $file 变量转换为数组,如果 $file 本身就是一个数组,则不做处理。
该代码使用了 PHP 中的逻辑非运算符 ! 和类型判断函数 is_array(),以及字符串函数 explode() 和转换函数 strtolower()。
首先,is_array() 函数用于检查变量是否为数组。如果 $file 不是数组,即 !is_array($file) 为 true,则执行括号内的代码。否则,跳过括号内的代码不执行。
注意区分函数 is_array()和 in_array()
在括号内的代码中,strtolower() 函数用于将 $file 变量中的所有字母转换为小写。
explode() 函数将 $file 变量按照点号 '.' 进行拆分,并返回拆分后的数组。例如,如果 $file 为 'example.jpg',则 explode('.', strtolower($file)) 返回一个包含两个元素的数组 ['example', 'jpg']。
最后,将拆分后的数组赋值给 $file 变量,从而将 $file 转换为数组。如果 $file 本身就是一个数组,则该代码不会执行任何操作,因为已经满足条件 !is_array($file) 为 false。
*/
$ext = end($file);
if(!in_array($ext,['jpg','png','gif'])) {
die('This file is not allowed!');
}
/*
文件名检查:对文件名进行检查,确保文件名中只包含 'jpg'、'png' 或 'gif' 这三种类型的图片文件扩展名,如果不是,则输出“提示错误的信息”并终止程序。
*/
$filename = reset($file).'.'.$file[count($file) - 1];
/*
这行代码的作用是根据数组 $file 中的元素来构建一个文件名。
reset($file) 函数返回数组 $file 的第一个元素的值。
count($file) - 1 返回数组 $file 中元素的数量减去 1,即最后一个元素的索引。
一般情况下,数组元素的索引是从 0 开始的。因此,数组中的第一个元素的索引是 0,第二个元素的索引是 1,依此类推。
举个例子,如果有一个包含三个元素的数组,它们的索引分别是 0、1 和 2,而不是 1、2 和 3。
$file[count($file) - 1] 表示使用最后一个元素的索引来获取数组 $file 中的最后一个元素的值。
'.' 是一个字符串,代表一个点号,用于连接文件名和文件扩展名。
将上面两步中得到的值拼接在一起,形成完整的文件名。
最终将完整的文件名赋值给变量 $filename。
例如,假设 $file 是一个包含 ['file', 'txt'] 的数组,那么该行代码的执行过程如下:
reset($file) 返回 'file'。
count($file) - 1 返回 1。
$file[count($file) - 1] 返回 'txt'。
'.' 表示点号。
将 'file'、. 和 'txt' 拼接在一起,得到最终的文件名 'file.txt'。
将文件名赋值给变量 $filename,即 $filename = 'file.txt'。
因此,最终 $filename 的值将是 'file.txt'。
*/
/*
最后,如果通过了上述检查,就将上传的文件移动到之前创建的目录 $sandbox 中,并输出成功或失败的信息。
*/
if (move_uploaded_file($_FILES['file']['tmp_name'], $sandbox . '/' . $filename)) {
echo 'Success!';
echo 'filepath:' . $sandbox . '/' .$filename;
} else {
echo 'Failed!';
}
/*
move_uploaded_file() 函数是 PHP 中用于将上传的文件移动到新位置的函数。
格式:
move_uploaded_file(string $filename, string $destination): bool
参数:
$filename:表示上传文件的临时路径,通常可以通过 $_FILES['file']['tmp_name'] 来获取。
$destination:表示文件移动后的目标路径,包括目录和文件名。
返回值:
如果文件成功移动到指定位置,则返回 true。
如果移动失败,则返回 false。
整个代码块的作用是:尝试将用户上传的文件移动到指定目录,并根据移动操作的结果输出相应的提示信息,告知用户文件上传是否成功。
*/
}
-->
以上的代码存在不一致可绕过的问题。
当文件上传功能依赖于文件名的后缀来判断文件类型时,攻击者可以利用控制 $file
数组中参数的顺序来绕过文件类型验证,打开代理,上传正确的文件格式,例如:shell.jpg。
这种情况通常与程序对文件名和后缀的处理方式有关,具体原因如下:
-
文件名和后缀的处理方式不一致:在某些情况下,程序可能会将文件名和后缀分开处理,而不是整体进行验证。如果攻击者能够控制文件名和后缀的顺序,就有可能绕过这种验证机制。
-
文件类型验证依赖于错误的信息:有些程序可能会错误地依赖文件名中的后缀来判断文件类型,而忽略了其他更可靠的验证方式,比如文件内容或者 MIME 类型。攻击者可以利用这一点来伪装文件类型,绕过验证。
举例来说,假设程序接受文件上传,并且使用文件名的后缀来判断文件类型,以下是一个攻击场景:
- 攻击者构造一个文件名为
shell.jpg.php
的文件,并上传到服务器。 - 由于程序只检查文件名的后缀,认为这是一个
.jpg
格式的图片文件,就会通过验证。 - 程序将该文件保存在服务器上,并且允许用户访问,从而导致攻击者成功上传了一个恶意的 PHP 脚本文件。
在这个例子中,攻击者成功地通过控制文件名中的后缀顺序,绕过了文件类型验证,并成功上传了恶意文件,实现了攻击目的。
上传文件,抓请求包
这是一个HTTP协议的POST请求
POST /HTTP/1.1
Host:192.168.3.186:10093
User-Agent:Mozilla/5.0(Windows NT 10.0;Win64;x64;rv:80.0)Gecko/20100101
Firefox/80.04
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;g=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding:gzip,deflate
Content-Type:multipart/form-data;
boundary=-----------15871529313486380551969503112
Content-Length:449R
0rigin:http://192.168.3.186:100930
DNT:1
Connection:close
Referer:http://192.168.3.186:10093/
Upgrade-lInsecure-Requests:1
-----------15871529313486380551969503112
Content-Disposition:formdata;name="filename"
-----------15871529313486380551969503112
Content-Disposition: form-data; name="file"; filename="shell.jpg"
Content-Type:image/jpeg
-----------15871529313486380551969503112
Content-Disposition:form-data:name="submit"
Submit
-----------15871529313486380551969503112--
请求包的解析:
# 请求头部分包括以下内容:
POST /HTTP/1.1
#
- 请求方法:POST
- 请求目标:/
- 协议版本:HTTP/1.1
Host:192.168.3.186:10093
HTTP请求头中的一个字段,表示请求目标的主机地址和端口。
在这个例子中,请求目标的主机地址是 192.168.3.186
,端口号是 10093
。
在HTTP协议中,任何请求都需要指定目标主机的地址和端口号,这是因为同一台服务器可能会提供多个服务(例如Web服务、FTP服务等),而端口号可以用来区分不同的服务。
如果没有指定端口号,则默认使用该服务的标准端口号。在Web服务中,标准端口号是80
User-Agent:Mozilla/5.0(Windows NT 10.0;Win64;x64;rv:80.0)Gecko/20100101 Firefox/80.0
是HTTP请求头中的一个字段,表示发起请求的用户代理(User-Agent)信息。User-Agent字段:指定客户端使用的浏览器类型和版本信息
具体来说,该字段中包含了浏览器或其他客户端程序的相关信息,例如所使用的操作系统、浏览器名称和版本号等。
-
Mozilla/5.0
:这部分通常称为产品标记(Product Token),它指示了用户代理是基于Mozilla浏览器引擎构建的。在过去,存在一些历史原因,使得大多数浏览器都使用了"Mozilla"作为他们的产品标记。 -
(Windows NT 10.0; Win64; x64)
:这部分描述了操作系统的信息。在这个例子中,表示用户代理运行在 Windows NT 10.0 操作系统上,并且该操作系统是64位的。在"Windows NT 10.0"中,"NT"代表"New Technology",表示这是Windows操作系统的一种变体,基于"New Technology"内核。"10.0"指示了Windows 10的版本号。
-
rv:80.0
:这部分表示浏览器的版本号。在这个例子中,表示浏览器的版本是80.0。 -
Gecko/20100101
:这部分表示浏览器引擎的相关信息。表示使用了 Gecko 渲染引擎,它是 Firefox 浏览器的核心组成部分。,并且版本号是20100101。 -
User-Agent: Firefox/80.0
:这个字段标识了发起请求的客户端应用程序或浏览器的名称和版本号。在这个例子中,用户代理表示请求是由Firefox浏览器的80.0版本发起的。
综上所述,整个User-Agent
字段表示该请求是由一个基于Mozilla浏览器引擎(Gecko)构建的、运行在 Windows NT 10.0 操作系统上的64位浏览器(版本号80.0)发起的。
服务端通常会根据User-Agent
字段来判断请求来源的客户端类型和版本,从而做出相应的响应。例如,服务端可能会根据客户端浏览器的类型和版本来优化网页显示、选择合适的文件格式、提供符合客户端能力的功能等等。
需要注意的是,User-Agent
字段并不是强制要求的,有些客户端程序可能并不会发送该字段,或者发送的内容并不准确。此外,有些恶意程序也可能伪造User-Agent
字段,因此服务端不能完全信任该字段的内容。
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;g=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding:gzip,deflate
Accept字段:指定客户端能够接收的内容类型及优先级
Accept-Language字段:指定客户端能够接收的自然语言
Accept-Encoding字段:指定客户端能够接收的编码格式
-
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
:这个字段指示了客户端可以接受的响应内容类型(MIME类型)。它是一个逗号分隔的列表,每个元素都有一个权重值(q值)来表示其优先级。在这个例子中,客户端能够接受的请求头中的内容类型首选是"text/html",然后是"application/xhtml+xml"、"application/xml",最后是"image/webp",如果服务器没有提供以上类型,则可以接受任意类型"/"。权重值越高,表示优先级越高。 -
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
:这个字段指示了客户端偏好的语言。它也是一个逗号分隔的列表,每个元素都有一个权重值来表示其优先级。在这个例子中,客户端首选的语言是"zh-CN"(简体中文),然后是"zh"(中文),接下来是"zh-TW"(繁体中文),"zh-HK"(香港中文),最后是"en-US"(美式英语)和"en"(英语)。权重值越高,表示优先级越高。 -
Accept-Encoding: gzip, deflate
:这个字段指示了客户端能够解码的响应内容编码方式。在这个例子中,客户端可以接受使用gzip或deflate算法进行压缩的响应内容。
Content-Type:multipart/form-data;
Content-Type字段:指定请求体中的媒体类型和字符集
Content-Type
是HTTP请求头中的一个字段,用于指定请求体(Request Body)的媒体类型。媒体类型描述了请求体中数据的格式和结构。
Content-Type
的值是multipart/form-data
。这个值表示请求体中包含了多个部分(multipart),每个部分都有自己的数据类型和格式。multipart/form-data
通常用于向服务器提交包含文件上传或表单数据的请求。
当使用multipart/form-data
时,请求体会被划分为多个部分,每个部分都有自己的头部信息和数据内容。这些部分之间通过边界(boundary)进行分隔,边界是一个唯一的字符串,用于用于分隔不同的数据字段、标识不同的部分。服务器端可以通过解析请求体的边界来获取每个部分的数据。
Content-Type
字段还可以指定其他类型的媒体,例如application/json
表示请求体是JSON格式的数据,application/x-www-form-urlencoded
表示请求体是经过URL编码的表单数据等。
综上所述,Content-Type: multipart/form-data
表示请求体中包含多个部分(multipart),用于上传文件或提交表单数据。
boundary=-----------15871529313486380551969503112
boundary分割线:用于分隔不同的数据块,这里使用"---15871529313486380551969503112"作为boundary。
Content-Length:449R
Content-Length字段:指定请求体的长度,449字节
0rigin:http://192.168.3.186:100930
Origin字段:指定请求来源
DNT:1
DNT字段:表示是否启用“不跟踪”功能,本例中为1,表示启用了该功能。
Connection:close
Connection字段:指定连接类型,本例中为close,表示在完成请求后关闭连接。
Referer:http://192.168.3.186:10093/
Referer字段:指定当前页面的来源地址,http://192.168.3.186:10093/
Upgrade-lInsecure-Requests:1
Upgrade-Insecure-Requests字段:表示客户端是否愿意升级到安全连接(https),本例中为1,表示愿意。
# 请求体部分包括以下内容:
-----------15871529313486380551969503112
Content-Disposition:formdata;name="filename"
第一个消息部分表示一个表单字段,它的 Content-Disposition
值为 "form-data"
,name
属性值为 "filename"
。这个字段的值为空。
Content-Disposition字段:指定数据块的类型和名称,本例中指定了一个文件类型的数据块,名为"file",文件名为"shell.jpg"。
-----------15871529313486380551969503112
Content-Disposition: form-data; name="file"; filename="shell.jpg"
Content-Type:image/jpeg
第二个消息部分表示一个文件,它的 Content-Disposition
值为 "form-data"
,name
属性值为 "file"
,filename
属性值为 "shell.jpg"
,
Content-Type字段:指定数据块的媒体类型和字符集,本例中指定了文件的 MIME 类型为 image/jpeg
。这个消息部分的内容是文件的二进制数据。
数据块内容:实际的文件内容。
-----------15871529313486380551969503112
Content-Disposition:form-data:name="submit"
Submit
-----------15871529313486380551969503112--
第三个消息部分表示另一个表单字段
提交按钮数据块:用于提交表单数据,名为"submit",值为"Submit"。
这个POST请求的目的是向服务器上传一个名为"shell.jpg"的图片文件
请求体以 --
开头和结尾,后面紧跟着边界标识符,表示请求体的结束。
对抓到的包进行修改:
目的,绕过限制
对比:之前
之后:
"filename[0]"、"filename[2]"、"filename[5]" 都是用于标识上传文件的参数名,可以根据需要进行更改。
"<?php eval($_POST[cmd]);?>" 的含义是,将通过POST请求发送的"cmd"参数作为字符串解析为PHP代码,并执行该代码。
具体解释如下:
- "<?php ?>" 是PHP代码的起始和结束标记。
- "eval()" 是一个PHP函数,用于执行字符串中的PHP代码。由于
eval()
函数可以执行任意代码,因此潜在地存在安全风险。建议谨慎处理用户输入,并严格限制eval()函数的使用。,以防止恶意代码注入和安全漏洞。 - "$_POST" 是一个超全局变量,用于获取通过POST请求发送的参数值。
- "[cmd]" 表示从$_POST变量中获取名为"cmd"的参数值。