CTF区块链题目环境的防抄袭方案

随着区块链领域逐渐被越来越多的人关注,在CTF比赛中也出现了一些区块链安全方向的赛题,目前以以太坊的智能合约的题目为主,主要考察点在于Solidity语言特性与EVM虚拟机特性。这些题目的合约被部署在以太坊公开的测试网上(如Ropsten),需要选手完成如清空合约余额、触发指定event或改变指定变量状态等操作以获取flag。

但由于公链的特性,链上的所有数据都是公开的且其中记录了所有挑战者发出的交易的详细信息,选手可以通过区块链浏览器查找链上的历史交易来发现其他人的解题思路来快速解题,也就是大家通常所说的“抄作业”问题,这造成了比赛的不公平,因此本文分享了一种解决此问题的方案。

思路来源

在设计billboard这道Cosmos SDK实现的公链题目的时候,我就考虑到了如何避免做题思路抄袭的问题。最初的想法是通过禁用查询交易的RPC接口来实现,但这会使选手做题体验不佳以及给服务端的判题环境造成极大的不便,之后又想到只允许选手查询自己所发的交易的思路,但这本质上给查询RPC加入通过签名实现的鉴权功能,工作量极大且破坏的公链的原有设计机制。

之后我想到查询交易的信息需要的参数是交易ID(交易hash),如果禁用了区块查询RPC接口,那么选手将无法得到其他人所发的交易的ID,也就无法查询其他人所发的交易信息,从而避免了思路抄袭。因此在billboard这道题目中我最终通过修改代码禁用了Tendermint的区块链查询相关的RPC并关闭了节点的P2P同步功能解决了该问题。 https://github.com/iczc/tendermint/commit/42111f2a780d7910fcc7adac61d65628d0fa4ea7

后来我想这个思路其实是可以推而广之到以太坊相关的合约题目来解决所谓的抄作业问题,因为本质上以太坊的区块链浏览器也是通过RPC接口实现对链上数据的索引,于是设计了下文中的面向以太坊出题环境的解决方案。

解决方案

最初的想法也是通过修改go-ethereum的RPC模块来禁用查询区块相关的功能,然后用修改版的以太坊客户端部署PoA共识的私有链作为合约题目的部署环境,但这需要长期更新维护一个修改版的go-ethereum,较为繁琐。仔细思索了一下,最终的解决方案是使用Ngnix作为以太坊节点RPC端口的反向代理,通过该反向代理过滤某些RPC方法以实现禁用区块查询的功能。

我所了解的使用Ngnix编程式扩展进行访问控制配置的的解决方案有Lua和njs,前者来自于OpenResty的lua-nginx-module,而后者是ngnix的自带模块,因此选用了njs方案。

nginScript是为了NGINX和NGINX Plus而开发的JavaScript实现,它被设计用于在服务器端处理请求。它通过融入JavaScript代码对NGINX的配置语法进行扩展,以便实现复杂的配置。

具体实现方式很简单,我们通过njs来配置允许的RPC白名单,选手想要请求以太坊节点RPC的流量首先达到Nginx并由njs处理,njs对Post请求的body中的json进行解析,如果method在不在白名单中则返回401状态码,否在跳转到在Ngnix配置文件中定义的内部跳转@jsonrpc,且在nginx配置文件中配置了location @jsonrpc为以太坊节点RPC的反向代理,具体代码如下所示: https://github.com/chainflag/ctf-eth-env/blob/main/docker/rpcproxy/njs/eth-jsonrpc-access.js
https://github.com/chainflag/ctf-eth-env/blob/main/docker/rpcproxy/nginx.conf#L28

使用方式

  1. 下载ctf-eth-env项目
1
2
git clone https://github.com/chainflag/ctf-eth-env.git
cd ctf-eth-env
  1. 配置一个以太坊PoA共识的私有链作为题目环境:
  • 创新一个账户用作出块人
1
2
docker run -it --rm  -v `pwd`/config:/root/.ethereum ethereum/client-go account new
echo "your keystore password" > `pwd`/config/password.txt
  • 生成genesis文件
    这步可以通过go-ethereum中的puppeth工具实现,可以通过go install github.com/ethereum/go-ethereum/cmd/puppeth@latest的方式安装,如果没有go环境也可以直接下载编译好的Geth & Tools,其中包含此工具,下载地址:https://geth.ethereum.org/downloads/ 使用方式:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
$ puppeth

Please specify a network name to administer (no spaces, hyphens or capital letters please)
> genesis

What would you like to do? (default = stats)
 1. Show network stats
 2. Configure new genesis
 3. Track new remote server
 4. Deploy network components
> 2

What would you like to do? (default = create)
 1. Create new genesis from scratch
 2. Import already existing genesis
> 1

Which consensus engine to use? (default = clique)
 1. Ethash - proof-of-work
 2. Clique - proof-of-authority
> 2

How many seconds should blocks take? (default = 15)
> 5

Which accounts are allowed to seal? (mandatory at least one)
> 0x # 输入上一步生成的账户地址

Which accounts should be pre-funded? (advisable at least one)
> 0x # 输入上一步生成的账户地址

Should the precompile-addresses (0x1 .. 0xff) be pre-funded with 1 wei? (advisable yes)
> no

Specify your chain/network ID if you want an explicit one (default = random)
>

What would you like to do? (default = stats)
 1. Show network stats
 2. Manage existing genesis
 3. Track new remote server
 4. Deploy network components
> 2

 1. Modify existing configurations
 2. Export genesis configurations
 3. Remove genesis configuration
> 2

Which folder to save the genesis specs into? (default = current)
  Will create genesis.json, genesis-aleth.json, genesis-harmony.json, genesis-parity.json
> config
  1. 最后就可以使用docker-compose来启动配置好的Ngnix与go-ethereum啦~
1
docker-compose up -d

至此用作CTF题目合约部署的以太坊节点环境就搭建好了,选手访问该IP的8545端口(以太坊RPC的默认端口)的流量首先会被Ngnix解析过滤,在白名单中的请求方法才会被转发给以太坊节点处理。

其他

以太坊官方提供的测试网提供了相应的水龙头用于领取测试币,而使用自建的PoA测试链会面临一个问题,即选手做题需要用的ETH手续费如何获取,因此我使用Golang + Svelte实现了一个水龙头来作为本解决方案的一个补充,它会随以太坊私链一同启动。

更简单的解决方式可以根据选手队伍的token生成一个私钥继而生成以太坊地址,然后将该地址通过以太坊的genesis.json创世配置的alloc分发初始代币,并把私钥的生成规则公布给选手。但问题点在于比赛中如果依然有新队伍注册,这些新队伍不会被分发代币,因此自建测试币水龙头是一个更通用的解决方案。

结语

本项目的诞生是在和pikachu的一起参与下完成的,也是他(作为小白鼠)在第六届 xctf final 中初次使用,同样也感谢soreatu对readme的introduction的补充润色。

如果大家觉得这个思路不错且有效的话,欢迎来star,也希望大家在使用后进行反馈,将这个方案更好的完善。 https://github.com/chainflag/ctf-eth-env