services-config.xml
描述符文件会指定一个“Logging”XML 元素来描述日志记录的不同方面。它类似于以下内容:
<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Debug">
<properties>
<prefix>[BlazeDS]</prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
<filters>
<pattern>Endpoint.*</pattern>
<pattern>Service.*</pattern>
<pattern>Configuration</pattern>
</filters>
</target>
</logging>
target
标签可采用一个名为 level
的可选属性,用来指示日志级别。如果调试级别设置为太详细的级别,您的应用程序可能会将敏感数据写入日志文件。sprintf()
、FormatMessageW()
或 syslog()
。snprintf()
将一个命令行参数复制到缓冲区中。
int main(int argc, char **argv){
char buf[128];
...
snprintf(buf,128,argv[1]);
}
%x
)来读取堆栈中的内容,然后函数会作为即将格式化的参数使用。(在本例中,函数没有采用任何即将格式化的参数。)通过使用 %n
格式化指令,攻击者能够对堆栈进行写入,进而使 snprintf()
记下迄今为止输出的字节数,并将其传送给指定的参数(而不是直接从参数中读取数值,这是程序员最初设计的行为)。对于这种攻击,更为的复杂的形式是使用四条交错的写入来完全控制堆栈中某个指针的值。
printf("%d %d %1$d %1$d\n", 5, 9);
5 9 5 5
Example 1
中所述。syslog()
函数有时候可以这样使用:
...
syslog(LOG_ERR, cmdBuf);
...
syslog()
的第二个参数是格式字符串,因此 cmdBuf
中的任何格式化指令都会按照Example 1
中所述进行解释。syslog()
的正确使用方式:
...
syslog(LOG_ERR, "%s", cmdBuf);
...
sprintf()
、FormatMessageW()
、syslog()
、NSLog
或 NSString.stringWithFormat
示例 1:下面的代码将命令行参数作为 NSString.stringWithFormat:
中的 format string。
int main(int argc, char **argv){
char buf[128];
...
[NSString stringWithFormat:argv[1], argv[2] ];
}
%x
)来读取堆栈中的内容,然后函数会作为即将格式化的参数使用。(在本例中,函数没有采用任何即将格式化的参数。)
printf("%d %d %1$d %1$d\n", 5, 9);
5 9 5 5
Example 1
中所述。syslog()
函数有时候可以这样使用:
...
syslog(LOG_ERR, cmdBuf);
...
syslog()
的第二个参数是格式字符串,因此 cmdBuf
中的任何格式化指令都会按照Example 1
中所述进行解释。syslog()
的正确使用方式:示例 4:Apple 核心类提供了利用 format string 漏洞的有趣途径。
...
syslog(LOG_ERR, "%s", cmdBuf);
...
String.stringByAppendingFormat()
函数有时候可以这样使用:
...
NSString test = @"Sample Text.";
test = [test stringByAppendingFormat:[MyClass
formatInput:inputControl.text]];
...
stringByAppendingFormat()
的正确使用方式:
...
NSString test = @"Sample Text.";
test = [test stringByAppendingFormat:@"%@", [MyClass
formatInput:inputControl.text]];
...
strncpy()
),使用方式不正确也会引发漏洞。对内存的处理加之有关数据段大小和结构方面所存在种种错误假设,是导致大多数 buffer overflow 漏洞产生的根源。
void wrongNumberArgs(char *s, float f, int d) {
char buf[1024];
sprintf(buf, "Wrong number of %.512s");
}
strncpy()
),使用方式不正确也会引发漏洞。对内存的处理加之有关数据段大小和结构方面所存在种种错误假设,是导致大多数 buffer overflow 漏洞产生的根源。%d
格式说明符将一个浮点转换为 f
。
void ArgTypeMismatch(float f, int d, char *s, wchar *ws) {
char buf[1024];
sprintf(buf, "Wrong type of %d", f);
...
}
script
标签。
<script src="http://www.example.com/js/fancyWidget.js"></script>
www.example.com
以外的网站中,则该站点将依赖 www.example.com
来运行正确的非恶意代码。如果攻击者可以入侵 www.example.com
,则他们可以篡改 fancyWidget.js
的内容,损害站点安全。例如,他们可以将代码添加到 fancyWidget.js
中,窃取用户的机密数据。
String arg = request.getParameter("arg");
...
Intent intent = new Intent();
...
intent.setClassName(arg);
ctx.startActivity(intent);
...
Intent
。隐式的内部意图可能会使系统遭受对内部组件的中间人攻击。Intent
使用内部组件定义的自定义操作。隐式意图可以促进从任何给定外部组件调用意图,而无需了解特定组件。将两者结合起来使应用程序能够从所需的应用程序上下文外部访问为特定内部使用指定的意图。Intent
的能力可以实现各种严重程度不等的中间人攻击,从信息泄露、拒绝服务到远程代码执行,具体取决于 Intent
指定的内部操作的能力。Intent
。
...
val imp_internal_intent_action = Intent("INTERNAL_ACTION_HERE")
startActivity(imp_internal_intent_action)
...
PendingIntent
。隐式待定意图可能会导致安全漏洞,例如拒绝服务、私人和系统信息泄漏以及权限提升。Intent
。隐式意图有助于从任何给定的外部组件调用意图,使用通用名称和筛选器来确定执行。Intent
创建为 PendingIntent
,这可能允许将 Intent
发送到在预期时间上下文之外运行的非预期组件,从而使系统容易受到拒绝服务、私人和系统信息泄露以及权限提升等攻击途径。PendingIntent
。
...
val imp_intent = Intent()
val flag_mut = PendingIntent.FLAG_MUTABLE
val pi_flagmutable_impintintent = PendingIntent.getService(
this,
0,
imp_intent,
flag_mut
)
...
PendingIntent
,其标记值设置为 FLAG_MUTABLE
。使用标记值 FLAG_MUTABLE
创建的待定意图很容易在下游设置未指定的 Intent
字段,这样会修改 Intent
的容量并使系统容易受到攻击。PendingIntent
后修改其底层 Intent
可能会使系统容易受到攻击。这主要取决于底层 Intent
的整体功能。在大多数情况下,最佳实践是通过将 PendingIntent
标记设置为 FLAG_IMMUTABLE
来防止发生潜在问题。FLAG_MUTABLE
创建的 PendingIntent
。
...
val intent_flag_mut = Intent(Intent.ACTION_GTALK_SERVICE_DISCONNECTED, Uri.EMPTY, this, DownloadService::class.java)
val flag_mut = PendingIntent.FLAG_MUTABLE
val pi_flagmutable = PendingIntent.getService(
this,
0,
intent_flag_mut,
flag_mut
)
...
Intent
启动活动、启动服务或传递广播,可使攻击者能够任意启动内部应用程序组件、控制内部组件的行为,或通过临时授权间接访问内容提供者提供的受保护数据。Intent
的 Extra 捆绑包中嵌套的任意 Intent
。Intent
,通过调用 startActivity
、startService
或 sendBroadcast
来启动组件。Intent
,并使用该 Intent
启动活动。
...
Intent nextIntent = (Intent) getIntent().getParcelableExtra("next-intent");
startActivity(nextIntent);
...
chroot()
一类的操作所需要的提高了的权限。chroot()
,它必须先获取 root
权限。当受权限控制的操作完成后,程序应该马上丢弃 root
权限并且返回调用它的用户权限等级。chroot()
函数,将应用程序限制在 APP_HOME
下的 file system 的子集中,以防止攻击者通过程序访问位于其他地方的未经授权的文件。然后,代码打开一个由用户指定的文件并处理文件的内容。
...
chroot(APP_HOME);
chdir("/");
FILE* data = fopen(argv[1], "r+");
...
setuid()
进行调用,意味着应用程序仍在使用没有必要的 root
权限进行操作。任何由攻击者对应用程序实施的成功盗取都会导致发生权限扩大的攻击,因为所有的恶意操作都将以超级用户的权限执行。如果应用程序把权限等级降低到一个非 root
用户,则会显著减少很多潜在的破坏。
...
DATA log_msg TYPE bal_s_msg.
val = request->get_form_field( 'val' ).
log_msg-msgid = 'XY'.
log_msg-msgty = 'E'.
log_msg-msgno = '123'.
log_msg-msgv1 = 'VAL: '.
log_msg-msgv2 = val.
CALL FUNCTION 'BAL_LOG_MSG_ADD'
EXPORTING
I_S_MSG = log_msg
EXCEPTIONS
LOG_NOT_FOUND = 1
MSG_INCONSISTENT = 2
LOG_IS_FULL = 3
OTHERS = 4.
...
val
”提交字符串“FOO
”,则日志中会记录以下条目:
XY E 123 VAL: FOO
FOO XY E 124 VAL: BAR
”,则日志中会记录以下条目:
XY E 123 VAL: FOO XY E 124 VAL: BAR
var params:Object = LoaderInfo(this.root.loaderInfo).parameters;
var val:String = String(params["username"]);
var value:Number = parseInt(val);
if (value == Number.NaN) {
trace("Failed to parse val = " + val);
}
val
" 提交字符串 "twenty-one
",则日志中会记录以下条目:
Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
",则日志中会记录以下条目:
Failed to parse val=twenty-one
User logged out=badguy
...
string val = (string)Session["val"];
try {
int value = Int32.Parse(val);
}
catch (FormatException fe) {
log.Info("Failed to parse val= " + val);
}
...
val
”提交字符串“twenty-one
”,则日志中会记录以下条目:
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
”,则日志中会记录以下条目:
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
long value = strtol(val, &endPtr, 10);
if (*endPtr != '\0')
syslog(LOG_INFO,"Illegal value = %s",val);
...
val
" 提交字符串 "twenty-one
",则日志中会记录以下条目:
Illegal value=twenty-one
twenty-one\n\nINFO: User logged out=evil
",则日志中会记录以下条目:
INFO: Illegal value=twenty-one
INFO: User logged out=evil
...
01 LOGAREA.
05 VALHEADER PIC X(50) VALUE 'VAL: '.
05 VAL PIC X(50).
...
EXEC CICS
WEB READ
FORMFIELD(NAME)
VALUE(VAL)
...
END-EXEC.
EXEC DLI
LOG
FROM(LOGAREA)
LENGTH(50)
END-EXEC.
...
VAL
" 提交字符串 "FOO
",则日志中会记录以下条目:
VAL: FOO
FOO VAL: BAR
",则日志中会记录以下条目:
VAL: FOO VAL: BAR
<cflog file="app_log" application="No" Thread="No"
text="Failed to parse val="#Form.val#">
val
" 提交字符串 "twenty-one
",则日志中会记录以下条目:
"Information",,"02/28/01","14:50:37",,"Failed to parse val=twenty-one"
twenty-one%0a%0a%22Information%22%2C%2C%2202/28/01%22%2C%2214:53:40%22%2C%2C%22User%20logged%20out:%20badguy%22
",则日志中会记录以下条目:
"Information",,"02/28/01","14:50:37",,"Failed to parse val=twenty-one"
"Information",,"02/28/01","14:53:40",,"User logged out: badguy"
func someHandler(w http.ResponseWriter, r *http.Request){
r.parseForm()
name := r.FormValue("name")
logout := r.FormValue("logout")
...
if (logout){
...
} else {
log.Printf("Attempt to log out: name: %s logout: %s", name, logout)
}
}
logout
提交字符串“twenty-one
”,而且他可以创建一个名为“admin
”的用户,则日志中会记录以下条目:
Attempt to log out: name: admin logout: twenty-one
admin+logout:+1+++++++++++++++++++++++
",则日志中将记录以下条目:
Attempt to log out: name: admin logout: 1 logout: twenty-one
...
String val = request.getParameter("val");
try {
int value = Integer.parseInt(val);
}
catch (NumberFormatException nfe) {
log.info("Failed to parse val = " + val);
}
...
val
”提交字符串“twenty-one
”,则日志中会记录以下条目:
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
”,则日志中会记录以下条目:
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
Example 1
以适应 Android 平台。
...
String val = this.getIntent().getExtras().getString("val");
try {
int value = Integer.parseInt();
}
catch (NumberFormatException nfe) {
Log.e(TAG, "Failed to parse val = " + val);
}
...
var cp = require('child_process');
var http = require('http');
var url = require('url');
function listener(request, response){
var val = url.parse(request.url, true)['query']['val'];
if (isNaN(val)){
console.log("INFO: Failed to parse val = " + val);
}
...
}
...
http.createServer(listener).listen(8080);
...
val
”提交字符串“twenty-one
”,则日志中会记录以下条目:
INFO: Failed to parse val = twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
”,则日志中会记录以下条目:
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
long value = strtol(val, &endPtr, 10);
if (*endPtr != '\0')
NSLog("Illegal value = %s",val);
...
val
" 提交字符串 "twenty-one
",则日志中会记录以下条目:
INFO: Illegal value=twenty-one
twenty-one\n\nINFO: User logged out=evil
",则日志中会记录以下条目:
INFO: Illegal value=twenty-one
INFO: User logged out=evil
<?php
$name =$_GET['name'];
...
$logout =$_GET['logout'];
if(is_numeric($logout))
{
...
}
else
{
trigger_error("Attempt to log out: name: $name logout: $val");
}
?>
logout
提交字符串“twenty-one
”,而且他可以创建一个名为“admin
”的用户,则日志中会记录以下条目:
PHP Notice: Attempt to log out: name: admin logout: twenty-one
admin+logout:+1+++++++++++++++++++++++
",则日志中将记录以下条目:
PHP Notice: Attempt to log out: name: admin logout: 1 logout: twenty-one
name = req.field('name')
...
logout = req.field('logout')
if (logout):
...
else:
logger.error("Attempt to log out: name: %s logout: %s" % (name,logout))
logout
提交字符串“twenty-one
”,而且他可以创建一个名为“admin
”的用户,则日志中会记录以下条目:
Attempt to log out: name: admin logout: twenty-one
admin+logout:+1+++++++++++++++++++++++
",则日志中将记录以下条目:
Attempt to log out: name: admin logout: 1 logout: twenty-one
...
val = req['val']
unless val.respond_to?(:to_int)
logger.info("Failed to parse val")
logger.info(val)
end
...
val
”提交字符串“twenty-one
”,则日志中会记录以下条目:
INFO: Failed to parse val
INFO: twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
”,则日志中会记录以下条目:
INFO: Failed to parse val
INFO: twenty-one
INFO: User logged out=badguy
...
let num = Int(param)
if num == nil {
NSLog("Illegal value = %@", param)
}
...
val
”提交字符串“twenty-one
”,则日志中会记录以下条目:
INFO: Illegal value = twenty-one
twenty-one\n\nINFO: User logged out=evil
”,则日志中会记录以下条目:
INFO: Illegal value=twenty-one
INFO: User logged out=evil
...
Dim Val As Variant
Dim Value As Integer
Set Val = Request.Form("val")
If IsNumeric(Val) Then
Set Value = Val
Else
App.EventLog "Failed to parse val=" & Val, 1
End If
...
val
”提交字符串“twenty-one
”,则日志中会记录以下条目:
Failed to parse val=twenty-one
twenty-one%0a%0a+User+logged+out%3dbadguy
”,则日志中会记录以下条目:
Failed to parse val=twenty-one
User logged out=badguy
CREATE
命令。攻击者可以利用此参数修改发送到服务器的命令,并使用 CRLF 字符注入新命令。
...
final String foldername = request.getParameter("folder");
IMAPFolder folder = (IMAPFolder) store.getFolder("INBOX");
...
folder.doCommand(new IMAPFolder.ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol imapProtocol) throws ProtocolException {
try {
imapProtocol.simpleCommand("CREATE " + foldername, null);
} catch (Exception e) {
// Handle Exception
}
return null;
}
});
...
USER
和 PASS
命令。攻击者可以利用此参数修改发送到服务器的命令,并使用 CRLF 字符注入新命令。
...
String username = request.getParameter("username");
String password = request.getParameter("password");
...
POP3SClient pop3 = new POP3SClient(proto, false);
pop3.login(username, password)
...
VRFY
命令。攻击者可能会使用此参数修改发送到服务器的命令并使用 CRLF 字符注入新命令。
...
c, err := smtp.Dial(x)
if err != nil {
log.Fatal(err)
}
user := request.FormValue("USER")
c.Verify(user)
...
VRFY
命令。攻击者可以利用此参数修改发送到服务器的命令,并使用 CRLF 字符注入新命令。
...
String user = request.getParameter("user");
SMTPSSLTransport transport = new SMTPSSLTransport(session,new URLName(Utilities.getProperty("smtp.server")));
transport.connect(Utilities.getProperty("smtp.server"), username, password);
transport.simpleCommand("VRFY " + user);
...
VRFY
命令。攻击者可以利用此参数修改发送到服务器的命令,并使用 CRLF 字符注入新命令。
...
user = request.GET['user']
session = smtplib.SMTP(smtp_server, smtp_tls_port)
session.ehlo()
session.starttls()
session.login(username, password)
session.docmd("VRFY", user)
...
null
。Equals()
之前检查 Item
属性返回的字符串是否为 null
,从而可能会导致 null
dereference。
string itemName = request.Item(ITEM_NAME);
if (itemName.Equals(IMPORTANT_ITEM)) {
...
}
...
null
也就无关紧要了。”null
。malloc()
返回的指针之前,并没有检查内存是否分配成功。
buf = (char*) malloc(req_size);
strncpy(buf, xfer, req_size);
malloc()
的调用失败是不是因为 req_size
太大,还是因为在同一时刻处理的请求太多。或者是由于已累计超时的 memory leak 引起的。如果不对错误进行处理,就不会知道是什么原因。null
。compareTo()
之前,不会检查 getParameter()
返回的字符串是否为 null
,从而可能会造成 null
dereference。例 2:。以下代码显示了这样一个例子,一个系统属性被设置为了
String itemName = request.getParameter(ITEM_NAME);
if (itemName.compareTo(IMPORTANT_ITEM)) {
...
}
...
null
,随后间接引用它的程序员错误地认为该属性值是已定义的。
System.clearProperty("os.name");
...
String os = System.getProperty("os.name");
if (os.equalsIgnoreCase("Windows 95") )
System.out.println("Not supported");
null
也就无关紧要了。”NullException
。cmd
”的属性。如果攻击者可以控制程序的环境,从而使“cmd
”处于未定义状态,则它就会在尝试调用 Trim()
方法时抛出一个 null 指针异常。
string cmd = null;
...
cmd = Environment.GetEnvironmentVariable("cmd");
cmd = cmd.Trim();
null
的指针是否为 null
之前间接引用该指针,则会发生 check-after-dereference 错误。如果程序明确检查过 null
,并确定该指针为 null
,但仍继续间接引用该指针,则会出现 dereference-after-check 错误。此类错误通常是由于错别字或程序员疏忽造成的。如果程序明确将指针设置为 null
,但稍后却间接引用该指针,则将出现 dereference-after-store 错误。此错误通常是因为程序员在声明变量时将该变量初始化为 null
所致。ptr
不是 NULL
。当程序员间接引用该指针时,这个假设就会清晰的体现出来。当程序员检查 ptr
是否为 NULL
时,就会与该假设发生矛盾。当在 if
语句中检查时,如果 ptr
可以为 NULL
,则在其间接引用时也将为 NULL
,并引起 segmentation fault。示例 2:在下列代码中,程序员会确认变量
ptr->field = val;
...
if (ptr != NULL) {
...
}
ptr
为 NULL
,然后错误地对其进行间接引用。如果在 if
语句中检查 ptr
时其为 NULL
,则会发生 null
dereference,从而导致分段故障。示例 3:在下列代码中,程序员忘记了字符串
if (ptr == null) {
ptr->field = val;
...
}
'\0'
实际上为 0 还是 NULL
,从而间接引用 null 指针并引发分段故障。示例 4:在下列代码中,程序员会将变量
if (ptr == '\0') {
*ptr = val;
...
}
ptr
明确设置为 NULL
。之后,程序员会间接引用 ptr
,而未检查对象是否为 null
值。
*ptr = NULL;
...
ptr->field = val;
...
}
NullPointerException
。cmd
”的属性。如果攻击者可以控制程序的环境,从而使“cmd
”处于未定义状态,则它就会在尝试调用 trim()
方法时抛出一个 null 指针异常。
String val = null;
...
cmd = System.getProperty("cmd");
if (cmd)
val = util.translateCommand(cmd);
...
cmd = val.trim();
unserialize()
函数,则会出现 Object injection 漏洞。攻击者可以将经特殊技术处理的序列化字符串传递到易受攻击的 unserialize()
调用,导致任意 PHP 对象注入应用程序范围。这种漏洞的严重性取决于应用程序范围中可用的类。攻击者会对实施 PHP 幻数方法(如 __wakeup
或 __destruct
)的类感兴趣,因为他们可以执行这些方法中的代码。__destruct()
幻数方法并执行定义为类属性的系统命令的 PHP 类。还有使用用户提供的数据对 unserialize()
进行的不安全调用。
...
class SomeAvailableClass {
public $command=null;
public function __destruct() {
system($this->command);
}
}
...
$user = unserialize($_GET['user']);
...
Example 1
中,应用程序可能预期获得一个序列化的 User
对象,但攻击者实际上可能提供 SomeAvailableClass
的序列化版本,并为其 command
属性提供一个预定义值:
GET REQUEST: http://server/page.php?user=O:18:"SomeAvailableClass":1:{s:7:"command";s:8:"uname -a";}
$user
对象的其他引用,析构函数方法将被调用并执行攻击者提供的命令。unserialize()
时声明的不同类,该技术由 Stefan Esser 在 BlackHat 2010 会议上提出。利用该技术,攻击者可以重复使用现有代码以生成其自己的负载。YAML.load()
的反序列化数据的函数,则会出现 Object injection 漏洞。只要在反序列化时将类加载到应用程序中,攻击者就可以将经特殊技术处理的序列化字符串传递到易受攻击的 YAML.load()
调用,从而将任意 Ruby 对象注入程序中。这可能带来大量的各种攻击机会,如绕过验证逻辑找到跨站点脚本漏洞,允许通过看似硬编码值来进行 SQL 注入,甚至进行完整的代码执行。YAML.load()
进行的不安全调用。
...
class Transaction
attr_accessor :id
def initialize(num=nil)
@id = num.is_a?(Numeric) ? num : nil
end
def print_details
unless @id.nil?
print $conn.query("SELECT * FROM transactions WHERE id=#{@id}")
end
end
end
...
user = YAML.load(params[:user]);
user.print_details
...
Example 1
中,应用程序可能预期获取一个序列化的 User
对象,而该对象也恰好具有一个名为 print_details
的函数,但攻击者可能实际上会提供 Transaction
对象的序列化版本,并为其 @id
属性提供一个预定义值。因此,如下所示的请求可能会允许绕过用于确保 @id
为数值的验证检查
GET REQUEST: http://server/page?user=!ruby%2Fobject%3ATransaction%0Aid%3A4%20or%205%3D5%0A
user
参数获分配了 !ruby/object:Transaction\nid:4 or 5=5\n
。Transaction
类型的对象,将 @id
设置为 "4 or 5=5"
。当开发人员认为他们将调用 User#print_details()
时,其实现在他们将会调用 Transaction#print_details()
,且 Ruby 的字符串插值意味着 SQL 查询将被更改,以执行查询:SELECT * FROM transactions WHERE id=4 or 5=5
。由于添加了额外语句,所以查询将评估为 true
并将返回 transactions
表中的所有内容,而不是开发人员所期望的单行。YAML.load()
时声明的不同类,该技术由 Stefan Esser 在 BlackHat 2010 会议上提出。利用该技术,攻击者可以重复使用现有代码以生成其自己的负载。clone()
应调用 super.clone()
获取新的对象。clone()
的方法中,应通过调用 super.clone()
来获取新对象。如果类没有遵守该约定,那么子类的 clone()
方法将会返回一个错误的对象类型。super.clone()
而产生的 bug。由于 Kibitzer
实现 clone()
的方法的缘故,FancyKibitzer
的克隆方法将会返回类型为 Kibitzer
而非 FancyKibitzer
的对象。
public class Kibitzer implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Object returnMe = new Kibitzer();
...
}
}
public class FancyKibitzer extends Kibitzer
implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Object returnMe = super.clone();
...
}
}
Equals()
和 GetHashCode()
中的一个。a.Equals(b) == true
,那么 a.GetHashCode() == b.GetHashCode()
。 Equals()
,但没有重写 GetHashCode()
。
public class Halfway() {
public override boolean Equals(object obj) {
...
}
}